diff --git a/packages/protocol/src/contracts/modules/EpochTradeModule.sol b/packages/protocol/src/contracts/modules/EpochTradeModule.sol index 0852bb0c..7399b880 100644 --- a/packages/protocol/src/contracts/modules/EpochTradeModule.sol +++ b/packages/protocol/src/contracts/modules/EpochTradeModule.sol @@ -50,6 +50,11 @@ contract EpochTradeModule is IEpochTradeModule { ); } + position.updateCollateral( + Market.load().collateralAsset, + collateralAmount + ); + // Validate after trading that collateral is enough position.afterTradeCheck(); } @@ -126,7 +131,6 @@ contract EpochTradeModule is IEpochTradeModule { // with the collateral get vEth (Loan) uint256 vEthLoan = collateralAmount; // 1:1 - position.depositedCollateralAmount += collateralAmount; position.borrowedVEth += vEthLoan; if (tokenAmount < 0 || tokenAmountLimit < 0) { @@ -156,6 +160,11 @@ contract EpochTradeModule is IEpochTradeModule { // Refund excess vEth sent position.borrowedVEth -= refundAmountVEth; + position.updateCollateral( + Market.load().collateralAsset, + collateralAmount + ); + position.updateBalance( tokenAmount, tokenAmountVEth.toInt(), @@ -181,7 +190,6 @@ contract EpochTradeModule is IEpochTradeModule { // with the collateral get vGas (Loan) uint256 vGasLoan = (tokenAmount * -1).toUint(); //(collateralAmount).divDecimal(getReferencePrice()); // collatera / vEth = 1/1 ; vGas/vEth = 1/currentPrice - position.depositedCollateralAmount += collateralAmount; position.borrowedVGas += vGasLoan; if (tokenAmount > 0 || tokenAmountLimit > 0) { @@ -202,6 +210,11 @@ contract EpochTradeModule is IEpochTradeModule { params ); + position.updateCollateral( + Market.load().collateralAsset, + collateralAmount + ); + position.updateBalance( tokenAmount, tokenAmountVEth.toInt(), @@ -242,13 +255,11 @@ contract EpochTradeModule is IEpochTradeModule { uint256 delta = (tokenAmount - position.currentTokenAmount) .toUint(); // with the collateral get vEth (Loan) - position.depositedCollateralAmount += collateralAmount; - uint256 vEthLoan = position.depositedCollateralAmount; // 1:1 - position.borrowedVEth = vEthLoan; + uint256 vEthDeltaLoan = collateralAmount; // 1:1 SwapTokensExactOutParams memory params = SwapTokensExactOutParams({ epochId: position.epochId, - availableAmountInVEth: vEthLoan, + availableAmountInVEth: vEthDeltaLoan, availableAmountInVGas: 0, amountInLimitVEth: 0, amountInLimitVGas: 0, @@ -263,7 +274,9 @@ contract EpochTradeModule is IEpochTradeModule { uint256 tokenAmountVEth, uint256 tokenAmountVGas ) = swapTokensExactOut(params); - position.borrowedVEth -= refundAmountVEth; + // Adjust the delta loan with the refund + vEthDeltaLoan -= refundAmountVEth; + position.borrowedVEth += vEthDeltaLoan; position.updateBalance( delta.toInt(), @@ -272,11 +285,6 @@ contract EpochTradeModule is IEpochTradeModule { ); } else { // Reduce the position (LONG) - if (collateralAmount > 0) { - revert Errors.InvalidData( - "Long Position: Unexpected collateral" - ); - } int256 delta = (tokenAmount - position.currentTokenAmount); @@ -299,6 +307,11 @@ contract EpochTradeModule is IEpochTradeModule { position.updateBalance(delta, 0, delta); } + + position.updateCollateral( + Market.load().collateralAsset, + collateralAmount + ); } /** @@ -333,18 +346,21 @@ contract EpochTradeModule is IEpochTradeModule { // Increase the position (SHORT) int256 delta = (tokenAmount - position.currentTokenAmount); - int256 deltaLimit = (tokenAmountLimit - - position.currentTokenAmount); + int256 deltaLimit; + if (tokenAmountLimit >= position.currentTokenAmount) { + deltaLimit = 0; + } else { + deltaLimit = (tokenAmountLimit - position.currentTokenAmount); + } // with the collateral get vGas (Loan) - uint256 vGasLoan = (delta * -1).toUint(); // - position.depositedCollateralAmount += collateralAmount; + uint256 vGasLoan = (delta * -1).toUint(); position.borrowedVGas += vGasLoan; SwapTokensExactInParams memory params = SwapTokensExactInParams({ epochId: position.epochId, amountInVEth: 0, - amountInVGas: (delta * -1).toUint(), + amountInVGas: vGasLoan, amountOutLimitVEth: 0, amountOutLimitVGas: (deltaLimit * -1).toUint() }); @@ -362,13 +378,8 @@ contract EpochTradeModule is IEpochTradeModule { ); } else { // Decrease the position (SHORT) - if (collateralAmount > 0) { - revert Errors.InvalidData( - "Short Position: Unexpected collateral" - ); - } - int256 delta = (position.currentTokenAmount - tokenAmount); + position.borrowedVGas -= (delta * -1).toUint(); SwapTokensExactOutParams memory params = SwapTokensExactOutParams({ epochId: position.epochId, @@ -394,6 +405,11 @@ contract EpochTradeModule is IEpochTradeModule { 0 ); } + + position.updateCollateral( + Market.load().collateralAsset, + collateralAmount + ); } /** @@ -428,9 +444,6 @@ contract EpochTradeModule is IEpochTradeModule { ) internal { // TODO check if after settlement and use the settlement price - // Add sent collateral - position.depositedCollateralAmount += collateralAmount; - if (position.currentTokenAmount > 0) { // Close LONG position SwapTokensExactInParams memory params = SwapTokensExactInParams({ @@ -459,6 +472,8 @@ contract EpochTradeModule is IEpochTradeModule { position.borrowedVEth = 0; + position.updateCollateral(Market.load().collateralAsset, 0); + position.resetBalance(); } else { // Close SHORT position @@ -523,6 +538,8 @@ contract EpochTradeModule is IEpochTradeModule { position.borrowedVGas = 0; + position.updateCollateral(Market.load().collateralAsset, 0); + position.resetBalance(); } } diff --git a/packages/protocol/test/Foil.trade-module.t.sol b/packages/protocol/test/Foil.trade-module.t.sol index 97d07a2e..7eedd703 100644 --- a/packages/protocol/test/Foil.trade-module.t.sol +++ b/packages/protocol/test/Foil.trade-module.t.sol @@ -1,236 +1,463 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.2 <0.9.0; -import "forge-std/Test.sol"; import "cannon-std/Cannon.sol"; - -import {IFoil} from "../src/contracts/interfaces/IFoil.sol"; -import {IFoilStructs} from "../src/contracts/interfaces/IFoilStructs.sol"; -import {VirtualToken} from "../src/contracts/external/VirtualToken.sol"; -import {TickMath} from "../src/contracts/external/univ3/TickMath.sol"; -import "../src/contracts/interfaces/external/INonfungiblePositionManager.sol"; -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; -import "../src/contracts/storage/Position.sol"; -import {IMintableToken} from "../src/contracts/external/IMintableToken.sol"; +import "./FoilTradeTestHelper.sol"; +import "../src/synthetix/utils/DecimalMath.sol"; import "forge-std/console2.sol"; -contract FoilTradeModuleTest is Test { +contract FoilTradeModuleTest is FoilTradeTestHelper { using Cannon for Vm; + using DecimalMath for uint256; IFoil foil; address pool; address tokenA; address tokenB; uint256 epochId; + uint256 feeRate; + uint256 UNIT = 1e18; IMintableToken collateralAsset; + IUniswapV3Pool uniCastedPool; function setUp() public { - console2.log("setUp"); foil = IFoil(vm.getAddress("Foil")); collateralAsset = IMintableToken( vm.getAddress("CollateralAsset.Token") ); - collateralAsset.mint(10_000_000 ether, address(this)); - collateralAsset.approve(address(foil), 10_000_000 ether); - - console2.log("getEpoch"); + collateralAsset.mint(type(uint240).max, address(this)); + collateralAsset.approve(address(foil), type(uint240).max); (epochId, , , pool, tokenA, tokenB) = foil.getLatestEpoch(); - - console2.log("pool", pool); - console2.log("tokenA", tokenA); - console2.log("tokenB", tokenB); - console2.log("epochId", epochId); + uniCastedPool = IUniswapV3Pool(pool); + feeRate = uint256(uniCastedPool.fee()) * 1e12; } - function test_trade_long() public { - uint256 priceReference; - uint256 positionId_1; - priceReference = foil.getReferencePrice(epochId); - - console2.log("priceReference", priceReference); - - uint256 collateralAmount = 100_000 ether; - int24 lowerTick = 12200; - int24 upperTick = 12400; - - ( - uint256 amountTokenA, - uint256 amountTokenB, - uint256 liquidity - ) = getTokenAmountsForCollateralAmount( - collateralAmount, - lowerTick, - upperTick - ); - - console2.log("amountTokenA", amountTokenA); - console2.log("amountTokenB", amountTokenB); - console2.log("liquidity", liquidity); - - IFoilStructs.LiquidityPositionParams memory params = IFoilStructs - .LiquidityPositionParams({ - epochId: epochId, - amountTokenA: amountTokenA, - amountTokenB: amountTokenB, - collateralAmount: collateralAmount, - lowerTick: lowerTick, - upperTick: upperTick, - minAmountTokenA: 0, - minAmountTokenB: 0 - }); - - foil.createLiquidityPosition(params); - - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); - - // Create Long position - console2.log("Create Long position"); - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); - positionId_1 = foil.createTraderPosition( + function test_tradeLong_Only() public { + uint256 referencePrice; + uint256 positionId; + uint256 collateralForOrder = 10 ether; + int256 positionSize; + uint256 tokens; + uint256 fee; + uint256 accumulatedFee; + + StateData memory expectedStateData; + StateData memory currentStateData; + + addLiquidity(foil, pool, epochId, collateralForOrder * 100_000); // enough to keep price stable (no slippage) + + // Set position size + positionSize = int256(collateralForOrder / 100); + + fillCollateralStateData(foil, collateralAsset, currentStateData); + + referencePrice = foil.getReferencePrice(epochId); + tokens = uint256(positionSize).mulDecimal(referencePrice); + fee = tokens.mulDecimal(feeRate); + accumulatedFee += fee; + + expectedStateData.userCollateral = + currentStateData.userCollateral - + collateralForOrder; + expectedStateData.foilCollateral = + currentStateData.foilCollateral + + collateralForOrder; + expectedStateData.depositedCollateralAmount = collateralForOrder; + expectedStateData.currentTokenAmount = positionSize; + expectedStateData.vEthAmount = 0; + expectedStateData.vGasAmount = uint256(positionSize); + expectedStateData.borrowedVEth = tokens + fee; + expectedStateData.borrowedVGas = 0; + + // Create Long position (with enough collateral) + positionId = foil.createTraderPosition( epochId, - 10 ether, - .1 ether, + collateralForOrder, + positionSize, 0 ); - logPositionAndAccount(positionId_1); + + currentStateData = assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Create Long" + ); // Modify Long position (increase it) - console2.log("Modify Long position (increase it)"); - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); - foil.modifyTraderPosition(positionId_1, 10 ether, .2 ether, 0); - logPositionAndAccount(positionId_1); + referencePrice = foil.getReferencePrice(epochId); + tokens = uint256(positionSize).mulDecimal(referencePrice); + fee = tokens.mulDecimal(feeRate); + accumulatedFee += fee; + + expectedStateData.userCollateral = + currentStateData.userCollateral - + collateralForOrder; + expectedStateData.foilCollateral = + currentStateData.foilCollateral + + collateralForOrder; + expectedStateData.depositedCollateralAmount = collateralForOrder * 2; + expectedStateData.currentTokenAmount = positionSize * 2; + expectedStateData.vEthAmount = 0; + expectedStateData.vGasAmount = uint256(positionSize) * 2; + expectedStateData.borrowedVEth = + currentStateData.borrowedVEth + + tokens + + fee; + expectedStateData.borrowedVGas = 0; - // Modify Long position (decrease it) - console2.log("Modify Long position (decrease it)"); - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); - foil.modifyTraderPosition(positionId_1, 0 ether, .1 ether, 0); - logPositionAndAccount(positionId_1); + foil.modifyTraderPosition( + positionId, + 2 * collateralForOrder, + 2 * positionSize, + 0 + ); - // Modify Long position (close it) - console2.log("Modify Long position (close it)"); - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); - foil.modifyTraderPosition(positionId_1, 0 ether, 0, 0); - logPositionAndAccount(positionId_1); - } + currentStateData = assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Increase Long" + ); - function test_trade_long_cross_sides() public { - uint256 priceReference; - uint256 positionId_3; - priceReference = foil.getReferencePrice(epochId); + // Modify Long position (decrease it) + referencePrice = foil.getReferencePrice(epochId); + tokens = uint256(positionSize).mulDecimal(referencePrice); + fee = tokens.mulDecimal(feeRate); + accumulatedFee += fee; + + expectedStateData.userCollateral = currentStateData.userCollateral; + expectedStateData.foilCollateral = currentStateData.foilCollateral; + expectedStateData.depositedCollateralAmount = currentStateData + .depositedCollateralAmount; + expectedStateData.currentTokenAmount = positionSize; + expectedStateData.vEthAmount = 0; + expectedStateData.vGasAmount = uint256(positionSize); + expectedStateData.borrowedVEth = + currentStateData.borrowedVEth - + tokens + + fee; // discounting here because we are charging the fee on the vETH we get back + expectedStateData.borrowedVGas = 0; - console2.log("priceReference", priceReference); - IFoilStructs.LiquidityPositionParams memory params = IFoilStructs - .LiquidityPositionParams({ - epochId: epochId, - amountTokenB: 20000 ether, - amountTokenA: 1000 ether, - collateralAmount: 100_000 ether, - lowerTick: 12200, - upperTick: 12400, - minAmountTokenA: 0, - minAmountTokenB: 0 - }); + foil.modifyTraderPosition( + positionId, + 2 * collateralForOrder, // keep same collateral + positionSize, + 0 + ); - foil.createLiquidityPosition(params); - params.amountTokenB = 40000 ether; - params.amountTokenA = 2000 ether; - foil.createLiquidityPosition(params); + currentStateData = assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Decrease Long" + ); - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); + // Modify Long position (close it) + referencePrice = foil.getReferencePrice(epochId); + // fee for closing the position + fee = currentStateData.vGasAmount.mulDecimal(referencePrice).mulDecimal( + feeRate + ); + accumulatedFee += fee; + + expectedStateData.userCollateral = + currentStateData.userCollateral + + 2 * + collateralForOrder - + accumulatedFee; + expectedStateData.foilCollateral = + currentStateData.foilCollateral - + 2 * + collateralForOrder + + accumulatedFee; + expectedStateData.depositedCollateralAmount = 0; + expectedStateData.currentTokenAmount = 0; + expectedStateData.vEthAmount = 0; + expectedStateData.vGasAmount = 0; + expectedStateData.borrowedVEth = 0; + expectedStateData.borrowedVGas = 0; + + foil.modifyTraderPosition(positionId, 0 ether, 0, 0); + + assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Close Long" + ); + } - // Create Long position (another one) - console2.log("Create Long position (another one)"); - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); - positionId_3 = foil.createTraderPosition( + function test_trade_long_cross_sides_Only() public { + uint256 referencePrice; + uint256 positionId; + uint256 collateralForOrder = 10 ether; + int256 positionSize; + uint256 tokens; + uint256 fee; + uint256 accumulatedFee; + + StateData memory expectedStateData; + StateData memory currentStateData; + + addLiquidity(foil, pool, epochId, collateralForOrder * 100_000); // enough to keep price stable (no slippage) + + // Set position size + positionSize = int256(collateralForOrder / 100); + + fillCollateralStateData(foil, collateralAsset, currentStateData); + + referencePrice = foil.getReferencePrice(epochId); + tokens = uint256(positionSize).mulDecimal(referencePrice); + fee = tokens.mulDecimal(feeRate); + accumulatedFee += fee; + + expectedStateData.userCollateral = + currentStateData.userCollateral - + collateralForOrder; + expectedStateData.foilCollateral = + currentStateData.foilCollateral + + collateralForOrder; + expectedStateData.depositedCollateralAmount = collateralForOrder; + expectedStateData.currentTokenAmount = positionSize; + expectedStateData.vEthAmount = 0; + expectedStateData.vGasAmount = uint256(positionSize); + expectedStateData.borrowedVEth = tokens + fee; + expectedStateData.borrowedVGas = 0; + + accumulatedFee += uint256(positionSize) + .mulDecimal(referencePrice) + .mulDecimal(feeRate); + + // Create Long position (with enough collateral) + positionId = foil.createTraderPosition( epochId, - 10 ether, - .1 ether, + collateralForOrder, + positionSize, 0 ); - logPositionAndAccount(positionId_3); + + currentStateData = assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Create Long" + ); // Modify Long position (change side) - console2.log("Modify Long position (change side)"); - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); + referencePrice = foil.getReferencePrice(epochId); + // change sides means closing the long order and opening a short order + // fee for closing the position + fee = currentStateData.vGasAmount.mulDecimal(referencePrice).mulDecimal( + feeRate + ); + accumulatedFee += fee; + + // tokens and fee for opening the short order + tokens = uint256(positionSize).divDecimal(referencePrice); + fee = tokens.mulDecimal(feeRate); + accumulatedFee += fee; + + expectedStateData.userCollateral = currentStateData.userCollateral; + expectedStateData.foilCollateral = currentStateData.foilCollateral; + expectedStateData.depositedCollateralAmount = currentStateData + .depositedCollateralAmount; + expectedStateData.currentTokenAmount = positionSize * -1; + expectedStateData.vEthAmount = tokens - fee; + expectedStateData.vGasAmount = 0; + expectedStateData.borrowedVEth = 0; + expectedStateData.borrowedVGas = uint256(positionSize); + foil.modifyTraderPosition( - positionId_3, - 0 ether, - -.05 ether, - -.01 ether + positionId, + collateralForOrder, + -1 * positionSize, + 0 ); - logPositionAndAccount(positionId_3); - } - function test_trade_short() public { - uint256 priceReference; - uint256 positionId_2; - priceReference = foil.getReferencePrice(epochId); + currentStateData = assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Change sides Long" + ); + } - console2.log("priceReference", priceReference); - IFoilStructs.LiquidityPositionParams memory params = IFoilStructs - .LiquidityPositionParams({ - epochId: epochId, - amountTokenB: 20000 ether, - amountTokenA: 1000 ether, - collateralAmount: 100_000 ether, - lowerTick: 12200, - upperTick: 12400, - minAmountTokenA: 0, - minAmountTokenB: 0 - }); + function test_trade_short_Only() public { + uint256 referencePrice; + uint256 positionId; + uint256 collateralForOrder = 10 ether; + int256 positionSize; + uint256 tokens; + uint256 fee; + uint256 accumulatedFee; + + StateData memory expectedStateData; + StateData memory currentStateData; + + addLiquidity(foil, pool, epochId, collateralForOrder * 100_000); // enough to keep price stable (no slippage) + + // Set position size + positionSize = int256(collateralForOrder / 100); + + fillCollateralStateData(foil, collateralAsset, currentStateData); + + referencePrice = foil.getReferencePrice(epochId); + tokens = uint256(positionSize).mulDecimal(referencePrice); + fee = tokens.mulDecimal(feeRate); + accumulatedFee += fee; + + expectedStateData.userCollateral = + currentStateData.userCollateral - + collateralForOrder; + expectedStateData.foilCollateral = + currentStateData.foilCollateral + + collateralForOrder; + expectedStateData.depositedCollateralAmount = collateralForOrder; + expectedStateData.currentTokenAmount = -1 * positionSize; + expectedStateData.vEthAmount = tokens - fee; + expectedStateData.vGasAmount = 0; + expectedStateData.borrowedVEth = 0; + expectedStateData.borrowedVGas = uint256(positionSize); + + // Create Long position (with enough collateral) + positionId = foil.createTraderPosition( + epochId, + collateralForOrder, + -1 * positionSize, + 0 + ); - foil.createLiquidityPosition(params); - params.amountTokenB = 40000 ether; - params.amountTokenA = 2000 ether; - foil.createLiquidityPosition(params); + currentStateData = assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Create Short" + ); - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); + // Modify Short position (increase it) + referencePrice = foil.getReferencePrice(epochId); + tokens = uint256(positionSize).mulDecimal(referencePrice); + fee = tokens.mulDecimal(feeRate); + accumulatedFee += fee; + + expectedStateData.userCollateral = + currentStateData.userCollateral - + collateralForOrder; + expectedStateData.foilCollateral = + currentStateData.foilCollateral + + collateralForOrder; + expectedStateData.depositedCollateralAmount = collateralForOrder * 2; + expectedStateData.currentTokenAmount = positionSize * -2; + expectedStateData.vEthAmount = + currentStateData.vEthAmount + + tokens - + fee; + expectedStateData.vGasAmount = 0; + expectedStateData.borrowedVEth = 0; + expectedStateData.borrowedVGas = uint256(positionSize) * 2; - // Create Short position - console2.log("Create Short position"); - positionId_2 = foil.createTraderPosition( - epochId, - 10 ether, - -.1 ether, + foil.modifyTraderPosition( + positionId, + 2 * collateralForOrder, + -2 * positionSize, 0 ); - logPositionAndAccount(positionId_2); - // Modify Short position (increase it) - console2.log("Modify Short position (increase it)"); - foil.modifyTraderPosition(positionId_2, 10 ether, -.2 ether, -.1 ether); - logPositionAndAccount(positionId_2); + currentStateData = assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Increase Short" + ); // Modify Short position (decrease it) - console2.log("Modify Short position (decrease it)"); - foil.modifyTraderPosition(positionId_2, 0, -.05 ether, -.01 ether); - logPositionAndAccount(positionId_2); + referencePrice = foil.getReferencePrice(epochId); + tokens = uint256(positionSize).mulDecimal(referencePrice); + fee = tokens.mulDecimal(feeRate); + accumulatedFee += fee; + + expectedStateData.userCollateral = currentStateData.userCollateral; + expectedStateData.foilCollateral = currentStateData.foilCollateral; + expectedStateData.depositedCollateralAmount = currentStateData + .depositedCollateralAmount; + expectedStateData.currentTokenAmount = positionSize * -1; + expectedStateData.vEthAmount = + currentStateData.vEthAmount - + tokens - + fee; + expectedStateData.vGasAmount = 0; + expectedStateData.borrowedVEth = 0; + expectedStateData.borrowedVGas = uint256(positionSize); + + foil.modifyTraderPosition( + positionId, + 2 * collateralForOrder, // keep same collateral + -1 * positionSize, + 0 + ); + + currentStateData = assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Decrease Short" + ); // Modify Short position (close it) - console2.log("Modify Short position (close it)"); - foil.modifyTraderPosition(positionId_2, 0, 0, 0); - logPositionAndAccount(positionId_2); + referencePrice = foil.getReferencePrice(epochId); + // fee for closing the position + fee = currentStateData.vGasAmount.mulDecimal(referencePrice).mulDecimal( + feeRate + ); + accumulatedFee += fee; + + expectedStateData.userCollateral = + currentStateData.userCollateral + + 2 * + collateralForOrder - + accumulatedFee; + expectedStateData.foilCollateral = + currentStateData.foilCollateral - + 2 * + collateralForOrder + + accumulatedFee; + expectedStateData.depositedCollateralAmount = 0; + expectedStateData.currentTokenAmount = 0; + expectedStateData.vEthAmount = 0; + expectedStateData.vGasAmount = 0; + expectedStateData.borrowedVEth = 0; + expectedStateData.borrowedVGas = 0; + + foil.modifyTraderPosition(positionId, 0 ether, 0, 0); + + assertPosition( + foil, + positionId, + collateralAsset, + expectedStateData, + "Close Short" + ); } - function test_trade_short_cross_sides_Only() public { - uint256 priceReference; + function test_trade_short_cross_sides() public { + uint256 referencePrice; uint256 positionId_4; - priceReference = foil.getReferencePrice(epochId); + referencePrice = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); + console2.log("referencePrice", referencePrice); IFoilStructs.LiquidityPositionParams memory params = IFoilStructs .LiquidityPositionParams({ epochId: epochId, @@ -248,8 +475,8 @@ contract FoilTradeModuleTest is Test { params.amountTokenA = 2000 ether; foil.createLiquidityPosition(params); - priceReference = foil.getReferencePrice(epochId); - console2.log("priceReference", priceReference); + referencePrice = foil.getReferencePrice(epochId); + console2.log("referencePrice", referencePrice); // Create Short position (another one) console2.log("Create Short position (another one)"); @@ -259,60 +486,11 @@ contract FoilTradeModuleTest is Test { -.1 ether, 0 ); - logPositionAndAccount(positionId_4); + logPositionAndAccount(foil, positionId_4); // Modify Short position (change side) console2.log("Modify Short position (change side)"); foil.modifyTraderPosition(positionId_4, 0, .05 ether, 0); - logPositionAndAccount(positionId_4); - } - - function getTokenAmountsForCollateralAmount( - uint256 collateralAmount, - int24 lowerTick, - int24 upperTick - ) - public - view - returns (uint256 loanAmount0, uint256 loanAmount1, uint256 liquidity) - { - (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(pool).slot0(); - console.log("sqrtPriceX96", sqrtPriceX96); - - uint160 sqrtPriceAX96 = uint160(TickMath.getSqrtRatioAtTick(lowerTick)); - uint160 sqrtPriceBX96 = uint160(TickMath.getSqrtRatioAtTick(upperTick)); - - console2.log("sqrtPriceAX96", sqrtPriceAX96); - console2.log("sqrtPriceBX96", sqrtPriceBX96); - (loanAmount0, loanAmount1, liquidity) = foil.getTokenAmounts( - epochId, - collateralAmount, - sqrtPriceX96, - sqrtPriceAX96, - sqrtPriceBX96 - ); - } - - function logPositionAndAccount(uint256 positionId) public { - Position.Data memory position = foil.getPosition(positionId); - console2.log(" >>> Position", positionId); - console2.log(" >>> Ids"); - console2.log(" >> tokenId : ", position.tokenId); - console2.log(" >> epochId : ", position.epochId); - // console2.log(" >> kind : ", position.kind); - console2.log(" >>> Accounting data (debt and deposited collateral)"); - console2.log( - " >> depositedCollateralAmount : ", - position.depositedCollateralAmount - ); - console2.log(" >> borrowedVEth : ", position.borrowedVEth); - console2.log(" >> borrowedVGas : ", position.borrowedVGas); - console2.log(" >>> Position data (owned tokens and position size)"); - console2.log(" >> vEthAmount : ", position.vEthAmount); - console2.log(" >> vGasAmount : ", position.vGasAmount); - console2.log( - " >> currentTokenAmount: ", - position.currentTokenAmount - ); + logPositionAndAccount(foil, positionId_4); } } diff --git a/packages/protocol/test/FoilTradeTestHelper.sol b/packages/protocol/test/FoilTradeTestHelper.sol new file mode 100644 index 00000000..a8953ac6 --- /dev/null +++ b/packages/protocol/test/FoilTradeTestHelper.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.2 <0.9.0; + +import "forge-std/Test.sol"; + +import {TickMath} from "../src/contracts/external/univ3/TickMath.sol"; +import {IFoil} from "../src/contracts/interfaces/IFoil.sol"; +import {IFoilStructs} from "../src/contracts/interfaces/IFoilStructs.sol"; +import {Position} from "../src/contracts/storage/Position.sol"; +import {IMintableToken} from "../src/contracts/external/IMintableToken.sol"; + +import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; + +abstract contract FoilTradeTestHelper is Test { + struct StateData { + uint256 userCollateral; + uint256 foilCollateral; + uint256 borrowedVEth; + uint256 borrowedVGas; + uint256 vEthAmount; + uint256 vGasAmount; + int256 currentTokenAmount; // position size + uint256 depositedCollateralAmount; + } + + function getTokenAmountsForCollateralAmount( + IFoil foil, + address pool, + uint256 epochId, + uint256 collateralAmount, + int24 lowerTick, + int24 upperTick + ) + public + view + returns (uint256 loanAmount0, uint256 loanAmount1, uint256 liquidity) + { + (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(pool).slot0(); + + uint160 sqrtPriceAX96 = uint160(TickMath.getSqrtRatioAtTick(lowerTick)); + uint160 sqrtPriceBX96 = uint160(TickMath.getSqrtRatioAtTick(upperTick)); + + (loanAmount0, loanAmount1, liquidity) = foil.getTokenAmounts( + epochId, + collateralAmount, + sqrtPriceX96, + sqrtPriceAX96, + sqrtPriceBX96 + ); + } + + function fillPositionState( + IFoil foil, + uint256 positionId, + StateData memory stateData + ) public { + Position.Data memory position = foil.getPosition(positionId); + stateData.depositedCollateralAmount = position + .depositedCollateralAmount; + stateData.vEthAmount = position.vEthAmount; + stateData.vGasAmount = position.vGasAmount; + stateData.currentTokenAmount = position.currentTokenAmount; + stateData.borrowedVEth = position.borrowedVEth; + stateData.borrowedVGas = position.borrowedVGas; + } + + function fillCollateralStateData( + IFoil foil, + IMintableToken collateralAsset, + StateData memory stateData + ) public { + stateData.userCollateral = collateralAsset.balanceOf(address(this)); + stateData.foilCollateral = collateralAsset.balanceOf(address(foil)); + } + + function assertPosition( + IFoil foil, + uint256 positionId, + IMintableToken collateralAsset, + StateData memory expectedStateData, + string memory stage + ) public returns (StateData memory currentStateData) { + fillCollateralStateData(foil, collateralAsset, currentStateData); + fillPositionState(foil, positionId, currentStateData); + + assertApproxEqRel( + currentStateData.userCollateral, + expectedStateData.userCollateral, + 0.0000001 ether, + string.concat(stage, " userCollateral") + ); + assertApproxEqRel( + currentStateData.foilCollateral, + expectedStateData.foilCollateral, + 0.0000001 ether, + string.concat(stage, " foilCollateral") + ); + assertEq( + currentStateData.depositedCollateralAmount, + expectedStateData.depositedCollateralAmount, + string.concat(stage, " depositedCollateralAmount") + ); + assertApproxEqRel( + currentStateData.currentTokenAmount, + expectedStateData.currentTokenAmount, + 0.01 ether, + string.concat(stage, " currentTokenAmount") + ); + assertApproxEqRel( + currentStateData.vEthAmount, + expectedStateData.vEthAmount, + 0.01 ether, + string.concat(stage, " vEthAmount") + ); + assertApproxEqRel( + currentStateData.vGasAmount, + expectedStateData.vGasAmount, + 0.01 ether, + string.concat(stage, " vGasAmount") + ); + assertApproxEqRel( + currentStateData.borrowedVEth, + expectedStateData.borrowedVEth, + 0.01 ether, + string.concat(stage, " borrowedVEth") + ); + assertApproxEqRel( + currentStateData.borrowedVGas, + expectedStateData.borrowedVGas, + 0.01 ether, + string.concat(stage, " borrowedVGas") + ); + } + + function logPositionAndAccount(IFoil foil, uint256 positionId) public { + Position.Data memory position = foil.getPosition(positionId); + console2.log(" >>> Position", positionId); + console2.log(" >>> Ids"); + console2.log(" >> tokenId : ", position.tokenId); + console2.log(" >> epochId : ", position.epochId); + // console2.log(" >> kind : ", position.kind); + console2.log(" >>> Accounting data (debt and deposited collateral)"); + console2.log( + " >> depositedCollateralAmount : ", + position.depositedCollateralAmount + ); + console2.log(" >> borrowedVEth : ", position.borrowedVEth); + console2.log(" >> borrowedVGas : ", position.borrowedVGas); + console2.log(" >>> Position data (owned tokens and position size)"); + console2.log(" >> vEthAmount : ", position.vEthAmount); + console2.log(" >> vGasAmount : ", position.vGasAmount); + console2.log( + " >> currentTokenAmount: ", + position.currentTokenAmount + ); + } + + function addLiquidity( + IFoil foil, + address pool, + uint256 epochId, + uint256 collateralRequired + ) internal { + int24 lowerTick = 12200; + int24 upperTick = 12400; + + ( + uint256 amountTokenA, + uint256 amountTokenB, + + ) = getTokenAmountsForCollateralAmount( + foil, + pool, + epochId, + collateralRequired, + lowerTick, + upperTick + ); + + IFoilStructs.LiquidityPositionParams memory params = IFoilStructs + .LiquidityPositionParams({ + epochId: epochId, + amountTokenA: amountTokenA, + amountTokenB: amountTokenB, + collateralAmount: collateralRequired, + lowerTick: lowerTick, + upperTick: upperTick, + minAmountTokenA: 0, + minAmountTokenB: 0 + }); + + foil.createLiquidityPosition(params); + } + + function addPreTrade( + IFoil foil, + uint256 epochId, + uint256 collateral + ) internal { + int256 positionSize = int256(collateral / 100); + + foil.createTraderPosition(epochId, collateral, positionSize, 0); + } +}