diff --git a/README.md b/README.md index f6ba842e..a90d9321 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,4 @@ In `/packages/protcol`: * Increment the version number in the cannonfiles * Run `pnpm simulate-deploy:sepolia` to verify there are no issues -* Run `CANNON_PRIVATE_KEY=x CANNON_PROVIDER_URL=y pnpm deploy sepolia` (or set those values in a `.env` file) \ No newline at end of file +* Run `CANNON_PRIVATE_KEY=x CANNON_PROVIDER_URL=y pnpm deploy:sepolia` (or set those values in a `.env` file) \ No newline at end of file diff --git a/packages/app/src/lib/components/chart.tsx b/packages/app/src/lib/components/chart.tsx index 8c2bf188..56cda198 100644 --- a/packages/app/src/lib/components/chart.tsx +++ b/packages/app/src/lib/components/chart.tsx @@ -1,11 +1,10 @@ import type React from 'react'; -import { useContext } from 'react'; +import { useContext, useEffect, useRef, useState } from 'react'; import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, - Tooltip, ComposedChart, Bar, ReferenceLine, @@ -16,38 +15,51 @@ import { colors } from '~/lib/styles/theme/colors'; const CustomBarShape = ({ x, - y, width, payload, - yAxis, + yAxisDomain, + chartHeight, + gridOffsetFromParent, }: { - x: any; - y: any; - width: any; + x: number; + width: number; payload: any; - yAxis: any; + yAxisDomain: any; + chartHeight: number; + gridOffsetFromParent: number; }) => { const candleColor = - colors.green && - colors.red && - (payload.open < payload.close ? colors.green[400] : colors.red[500]); - const barHeight = Math.abs(yAxis(payload.open) - yAxis(payload.close)); - const wickHeight = Math.abs(yAxis(payload.low) - yAxis(payload.high)); - const wickY = Math.min(yAxis(payload.low), yAxis(payload.high)); - const barY = Math.min(yAxis(payload.open), yAxis(payload.close)); + payload.open < payload.close + ? colors.green?.[400] ?? '#00FF00' + : colors.red?.[500] ?? '#FF0000'; + + const scaleY = (value: number) => { + const scaled = (value - yAxisDomain[0]) / (yAxisDomain[1] - yAxisDomain[0]); + return chartHeight - scaled * chartHeight + gridOffsetFromParent; + }; + + const lowY = scaleY(payload.low); + const highY = scaleY(payload.high); + const openY = scaleY(payload.open); + const closeY = scaleY(payload.close); + + const barHeight = Math.abs(openY - closeY); + const wickHeight = Math.abs(lowY - highY); return ( <> + {/* Wick */} + {/* Body */} { + const grayColor = colors.gray?.[800] ?? '#808080'; + const { averagePrice, prices } = useContext(MarketContext); - console.log('prices:', prices); + + const yAxisDomain = [ + Math.min(...prices.map((p) => p.low)), + Math.max(...prices.map((p) => p.high)), + ]; + + const chartRef = useRef(null); + const [gridHeight, setGridHeight] = useState(0); + const [gridOffsetFromParent, setGridOffsetFromParent] = useState(0); + + useEffect(() => { + if (chartRef.current) { + // Access the parent container and the CartesianGrid's bounding boxes + const parentElement = (chartRef.current as any).container; + const gridElement = parentElement.querySelector( + '.recharts-cartesian-grid' + ); + + if (gridElement && parentElement) { + const gridRect = gridElement.getBoundingClientRect(); + const parentRect = parentElement.getBoundingClientRect(); + + // Calculate the height of the CartesianGrid + setGridHeight(gridRect.height); + + // Calculate the offset from the top of the parent container + setGridOffsetFromParent(gridRect.top - parentRect.top); + } + } + }, [prices]); return ( - - + + - p.high))]} - tickFormatter={(value) => (value / 10e8).toFixed(2)} - /> - ((value as number) / 10e8).toFixed(2)} /> + ( - (d / 10e8) * (400 / 15)} - /> - )} + dataKey="candles" + // eslint-disable-next-line @typescript-eslint/no-explicit-any, react/no-unstable-nested-components + shape={(props: any) => { + return ( + + ); + }} /> diff --git a/packages/app/src/lib/layout/Header.tsx b/packages/app/src/lib/layout/Header.tsx index cff8f2f4..11b2db62 100644 --- a/packages/app/src/lib/layout/Header.tsx +++ b/packages/app/src/lib/layout/Header.tsx @@ -2,11 +2,12 @@ import { Box, Flex, Image } from '@chakra-ui/react'; import Link from 'next/link'; -import FoilTestnet from '@/protocol/deployments/11155111/Foil.json'; -import FoilLocal from '@/protocol/deployments/13370/Foil.json'; import ConnectButton from '../components/ConnectButton'; +import FoilTestnet from '@/protocol/deployments/11155111/Foil.json'; +import FoilLocal from '@/protocol/deployments/13370/Foil.json'; + const Header = () => { return ( { {/* Subscribe */} {/* Earn */} - - Local Market - + {process.env.NODE_ENV === 'development' && ( + + Local Market + + )} Testnet Market diff --git a/packages/data/src/processes/market.ts b/packages/data/src/processes/market.ts index ff2d2959..245a891b 100644 --- a/packages/data/src/processes/market.ts +++ b/packages/data/src/processes/market.ts @@ -21,6 +21,7 @@ export const indexMarketEvents = async (publicClient: PublicClient, Foil: { addr const processLogs = async (logs: Log[]) => { for (const log of logs) { const serializedLog = JSON.stringify(log, bigintReplacer); + // TODO: Make this upsert! const event = eventRepository.create({ logData: JSON.parse(serializedLog), // Parse back to JSON object contractId: `${chainId}:${Foil.address}`, @@ -60,6 +61,7 @@ export const indexMarketEventsRange = async (publicClient: PublicClient, start: topics: log.topics, }); const serializedLog = JSON.stringify(decodedLog, bigintReplacer); + // TODO: Make this upsert! const event = eventRepository.create({ logData: JSON.parse(serializedLog), // Parse back to JSON object contractId: `${await publicClient.getChainId()}:${contractAddress}`, diff --git a/packages/data/src/service.ts b/packages/data/src/service.ts index 41a39dac..38b8133f 100644 --- a/packages/data/src/service.ts +++ b/packages/data/src/service.ts @@ -85,7 +85,7 @@ createConnection(connectionOptions).then(async connection => { }); // Get average price over a specified time period filtered by contractId - app.get('/prices/average', async (req, res) => { +app.get('/prices/average', async (req, res) => { const { startTimestamp, endTimestamp, contractId } = req.query; const where: any = {}; @@ -160,6 +160,7 @@ createConnection(connectionOptions).then(async connection => { res.json(chartData); }); + app.get('/positions', async (req, res) => { const { contractId, isLP } = req.query; const where: any = {}; diff --git a/packages/data/src/worker.ts b/packages/data/src/worker.ts index 9ab96238..650846f9 100644 --- a/packages/data/src/worker.ts +++ b/packages/data/src/worker.ts @@ -57,15 +57,15 @@ async function createViemPublicClient(providerUrl: string) { } async function indexBaseFeePerGasRangeCommand(startBlock: number, endBlock: number, rpcUrl: string, contractAddress: string) { -console.log(`Indexing base fee per gas from block ${startBlock} to ${endBlock} for contract ${contractAddress} using ${rpcUrl}`); -const client = await createViemPublicClient(rpcUrl); -await indexBaseFeePerGasRange(client, startBlock, endBlock, contractAddress); + console.log(`Indexing base fee per gas from block ${startBlock} to ${endBlock} for contract ${contractAddress} using ${rpcUrl}`); + const client = await createViemPublicClient(rpcUrl); + await indexBaseFeePerGasRange(client, startBlock, endBlock, contractAddress); } async function indexMarketEventsRangeCommand(startBlock: number, endBlock: number, rpcUrl: string, contractAddress: string, contractAbi: Abi) { -console.log(`Indexing market events from block ${startBlock} to ${endBlock} for contract ${contractAddress} using ${rpcUrl}`); -const client = await createViemPublicClient(rpcUrl); -await indexMarketEventsRange(client, startBlock, endBlock, contractAddress, contractAbi); + console.log(`Indexing market events from block ${startBlock} to ${endBlock} for contract ${contractAddress} using ${rpcUrl}`); + const client = await createViemPublicClient(rpcUrl); + await indexMarketEventsRange(client, startBlock, endBlock, contractAddress, contractAbi); } if(process.argv.length < 3) { @@ -80,19 +80,9 @@ if(process.argv.length < 3) { } else { const args = process.argv.slice(2); if (args[0] === 'index-sepolia') { - // Index mainnet gas to sepolia contract - indexBaseFeePerGasRangeCommand(20413376, 20428947, 'https://ethereum-rpc.publicnode.com', `${sepolia.id}:${FoilSepolia.address}`) - //indexMarketEventsRangeCommand(1722270000, 1722458027, 'https://ethereum-rpc.publicnode.com', FoilSepolia.address, FoilSepolia.abi as Abi) - } else if (args[0] === 'index-base-fee-per-gas') { - const [start, end, rpcUrl, contractAddress] = args.slice(1); - indexBaseFeePerGasRangeCommand(Number(start), Number(end), rpcUrl, contractAddress) - .then(() => console.log('Indexing completed successfully')) - .catch(error => console.error('Error indexing base fee per gas range:', error)); - } else if (args[0] === 'index-market-events') { - const [start, end, rpcUrl, contractAddress, contractAbiPath] = args.slice(1); - const contractAbi = require(contractAbiPath) as Abi; // Assuming the ABI is provided as a JSON file path - indexMarketEventsRangeCommand(Number(start), Number(end), rpcUrl, contractAddress, contractAbi) - .then(() => console.log('Indexing completed successfully')) - .catch(error => console.error('Error indexing market events range:', error)); + Promise.all([ + indexBaseFeePerGasRangeCommand(20413376, 20428947, 'https://ethereum-rpc.publicnode.com', `${sepolia.id}:${FoilSepolia.address}`), + indexMarketEventsRangeCommand(1722270000, 1722458027, 'https://ethereum-sepolia-rpc.publicnode.com', FoilSepolia.address, FoilSepolia.abi as Abi) + ]) } } \ No newline at end of file diff --git a/packages/protocol/cannonfile.sepolia.toml b/packages/protocol/cannonfile.sepolia.toml index f150ae99..c856a07d 100644 --- a/packages/protocol/cannonfile.sepolia.toml +++ b/packages/protocol/cannonfile.sepolia.toml @@ -1,10 +1,10 @@ name="foil" -version="0.5" +version="0.6" [var.settings] owner="0xE006B58cA5aB7ba53863012dc3067A14b965C1da" startTime = "1722270000" # Mon Jul 29 2024 16:20:00 GMT+0000 -endTime = "1722458027" # Wed Jul 31 2024 20:33:47 GMT+0000 +endTime = "1725195600" # Sunday, September 1, 2024 9:00:00 AM GMT-04:00 baseAssetMinPriceTick = "5200" # 1.709 baseAssetMaxPriceTick = "28200" # 17.09 feeRate = "10000" # 1% diff --git a/packages/protocol/cannonfile.toml b/packages/protocol/cannonfile.toml index 4d540221..ef5d818a 100644 --- a/packages/protocol/cannonfile.toml +++ b/packages/protocol/cannonfile.toml @@ -1,10 +1,10 @@ name="foil" -version="0.5" +version="0.6" [var.settings] owner = "0xEB045D78d273107348b0300c01D29B7552D622ab" startTime = "1722270000" # Mon Jul 29 2024 16:20:00 GMT+0000 -endTime = "1725900163" # Fri, 09 Sep 2024 16:42:43 GMT+0000 +endTime = "1725195600" # Sunday, September 1, 2024 9:00:00 AM GMT-04:00 baseAssetMinPriceTick = "5200" # 1.709 baseAssetMaxPriceTick = "28200" # 17.09 feeRate = "10000" # 1% diff --git a/packages/protocol/deployments/11155111/EpochConfigurationModule.json b/packages/protocol/deployments/11155111/EpochConfigurationModule.json index 743dd7da..e5c004be 100644 --- a/packages/protocol/deployments/11155111/EpochConfigurationModule.json +++ b/packages/protocol/deployments/11155111/EpochConfigurationModule.json @@ -1,5 +1,5 @@ { - "address": "0x233Fdf7aeD41DADcf21E684048649e84769807C5", + "address": "0x35f49BA660F4A65A3e411D6Ee9C97332b9Fb5Ac9", "abi": [ { "type": "function", @@ -233,12 +233,12 @@ ], "constructorArgs": [], "linkedLibraries": {}, - "deployTxnHash": "0x8c5d6ed2010e2a75000260198e7cb0b009bd1cf3770097874ba764674d995f2b", - "deployTxnBlockNumber": "6474235", - "deployTimestamp": "1723307100", + "deployTxnHash": "0x784d70f38d9e76198c86767878647a4b7d7aeff268cbce8cb810bafe1d867ed1", + "deployTxnBlockNumber": "6506105", + "deployTimestamp": "1723741740", "sourceName": "src/contracts/modules/EpochConfigurationModule.sol", "contractName": "EpochConfigurationModule", "deployedOn": "deploy.EpochConfigurationModule", "gasUsed": 2016847, - "gasCost": "1001154682" + "gasCost": "27304630816" } \ No newline at end of file diff --git a/packages/protocol/deployments/11155111/EpochLiquidityModule.json b/packages/protocol/deployments/11155111/EpochLiquidityModule.json index f9e558a1..c2e85348 100644 --- a/packages/protocol/deployments/11155111/EpochLiquidityModule.json +++ b/packages/protocol/deployments/11155111/EpochLiquidityModule.json @@ -1,5 +1,5 @@ { - "address": "0x4AB67EE0d21fE2816EBDCD2286c21e0728BF5a0A", + "address": "0xBFA7450ef1EE0De783d212F7d5566C8Db4C15F72", "abi": [ { "type": "function", @@ -486,12 +486,12 @@ ], "constructorArgs": [], "linkedLibraries": {}, - "deployTxnHash": "0x4dbcd6e7c95fe5ed1720baf9be28161605470f785c05c0a9484235b276a39bfa", - "deployTxnBlockNumber": "6474229", - "deployTimestamp": "1723307028", + "deployTxnHash": "0x49f297cafe18548d5c0431de649a9d9220750eb9390d876626bc040a27a6aa58", + "deployTxnBlockNumber": "6506098", + "deployTimestamp": "1723741644", "sourceName": "src/contracts/modules/EpochLiquidityModule.sol", "contractName": "EpochLiquidityModule", "deployedOn": "deploy.EpochLiquidityModule", - "gasUsed": 2082120, - "gasCost": "1071720725" + "gasUsed": 2068518, + "gasCost": "37401849570" } \ No newline at end of file diff --git a/packages/protocol/deployments/11155111/EpochNftModule.json b/packages/protocol/deployments/11155111/EpochNftModule.json index 7081d96b..9a5575cf 100644 --- a/packages/protocol/deployments/11155111/EpochNftModule.json +++ b/packages/protocol/deployments/11155111/EpochNftModule.json @@ -1,5 +1,5 @@ { - "address": "0xde6d24F43752BA9C4bd63fc9942d4384f3af00ac", + "address": "0xF1358121F4e1869821839ec04B50624077d4b6D7", "abi": [ { "type": "constructor", @@ -463,12 +463,12 @@ ], "constructorArgs": [], "linkedLibraries": {}, - "deployTxnHash": "0x7ac01cc6cc6101a034d757bc8a521aa3c2b4e401f8f295cf14b5cb70770b6382", - "deployTxnBlockNumber": "6474230", - "deployTimestamp": "1723307040", + "deployTxnHash": "0xc3bca3b335b4e296a71644c6072d3f914d87c41d0ea12dffe7f3c60dc885f615", + "deployTxnBlockNumber": "6506099", + "deployTimestamp": "1723741656", "sourceName": "src/contracts/modules/EpochNftModule.sol", "contractName": "EpochNftModule", "deployedOn": "deploy.EpochNftModule", "gasUsed": 1190314, - "gasCost": "1046361736" + "gasCost": "35710715745" } \ No newline at end of file diff --git a/packages/protocol/deployments/11155111/EpochTradeModule.json b/packages/protocol/deployments/11155111/EpochTradeModule.json index 72925e18..097272db 100644 --- a/packages/protocol/deployments/11155111/EpochTradeModule.json +++ b/packages/protocol/deployments/11155111/EpochTradeModule.json @@ -1,5 +1,5 @@ { - "address": "0x7adA6C864c9e0d8DE13334CE8F1F547bB3BF33D4", + "address": "0x6F10Da98ba11D9Fcc8aA272B86fd4da999968f4A", "abi": [ { "type": "function", @@ -231,11 +231,6 @@ } ] }, - { - "type": "error", - "name": "OverflowInt24ToUint256", - "inputs": [] - }, { "type": "error", "name": "OverflowInt256ToUint256", @@ -285,12 +280,12 @@ ], "constructorArgs": [], "linkedLibraries": {}, - "deployTxnHash": "0x1bbcb9af6846d4cb6c50fd53d8e343be7c27db1dc44184088aa6931c33ee16c8", - "deployTxnBlockNumber": "6474231", - "deployTimestamp": "1723307052", + "deployTxnHash": "0x9c449c4df523db2c645910233db4bf6fd5d31f28f73d95132967751f6052a466", + "deployTxnBlockNumber": "6506101", + "deployTimestamp": "1723741692", "sourceName": "src/contracts/modules/EpochTradeModule.sol", "contractName": "EpochTradeModule", "deployedOn": "deploy.EpochTradeModule", - "gasUsed": 2196653, - "gasCost": "1040865499" + "gasUsed": 1933722, + "gasCost": "33339990657" } \ No newline at end of file diff --git a/packages/protocol/deployments/11155111/EpochUMASettlementModule.json b/packages/protocol/deployments/11155111/EpochUMASettlementModule.json index b517ba86..d456ac2b 100644 --- a/packages/protocol/deployments/11155111/EpochUMASettlementModule.json +++ b/packages/protocol/deployments/11155111/EpochUMASettlementModule.json @@ -1,5 +1,5 @@ { - "address": "0xd2e77ff7a4a001D070E48eC3090219133f8c37d2", + "address": "0xEBAd76CA60B0524063aF9049f7E7c2a1DF1E442e", "abi": [ { "type": "function", @@ -157,12 +157,12 @@ ], "constructorArgs": [], "linkedLibraries": {}, - "deployTxnHash": "0x94a85817e24f209ca231d0d93830420880cf0352e49d850eb163b903135c6d36", - "deployTxnBlockNumber": "6474232", - "deployTimestamp": "1723307064", + "deployTxnHash": "0xbe91e256e5495a233b6ed50eccb59ac9a939e60189e1edba1f846a61365a108e", + "deployTxnBlockNumber": "6506102", + "deployTimestamp": "1723741704", "sourceName": "src/contracts/modules/EpochUMASettlementModule.sol", "contractName": "EpochUMASettlementModule", "deployedOn": "deploy.EpochUMASettlementModule", - "gasUsed": 768926, - "gasCost": "1025526924" + "gasUsed": 798576, + "gasCost": "33852750403" } \ No newline at end of file diff --git a/packages/protocol/deployments/11155111/EpochViewsModule.json b/packages/protocol/deployments/11155111/EpochViewsModule.json index d46dc53e..ec20c1bc 100644 --- a/packages/protocol/deployments/11155111/EpochViewsModule.json +++ b/packages/protocol/deployments/11155111/EpochViewsModule.json @@ -1,5 +1,5 @@ { - "address": "0x0660A1E92Ff10E2fA30fe971812A1Eb99b2d8313", + "address": "0xbA2477b4c97bB7BcCB386844F64B87bC8880Bc44", "abi": [ { "type": "function", @@ -232,12 +232,12 @@ ], "constructorArgs": [], "linkedLibraries": {}, - "deployTxnHash": "0x43fc7637f2e3e32338e8080ec625a984d222c9020f596a068821cc70ef466fc1", - "deployTxnBlockNumber": "6474233", - "deployTimestamp": "1723307076", + "deployTxnHash": "0x60c704db36efe9f0cecd378d49b2a9ffa5355469433d3900cd3b6c9bdb8d192f", + "deployTxnBlockNumber": "6506103", + "deployTimestamp": "1723741716", "sourceName": "src/contracts/modules/EpochViewsModule.sol", "contractName": "EpochViewsModule", "deployedOn": "deploy.EpochViewsModule", - "gasUsed": 443339, - "gasCost": "1001152855" + "gasUsed": 443327, + "gasCost": "31085799463" } \ No newline at end of file diff --git a/packages/protocol/deployments/11155111/Foil.json b/packages/protocol/deployments/11155111/Foil.json index 1db0d023..7eea3827 100644 --- a/packages/protocol/deployments/11155111/Foil.json +++ b/packages/protocol/deployments/11155111/Foil.json @@ -1,5 +1,5 @@ { - "address": "0xa088d9093076894185184c3b1e4ab524fc947720", + "address": "0x55f555451e9ca46bbc150e3117209a203b8c64e6", "abi": [ { "type": "function", @@ -1654,11 +1654,11 @@ } ], "deployedOn": "router.Foil", - "deployTxnHash": "0xe954629061d99bc95f0a2721a06458214f0f7910574057d59d1d94e007d13c70", - "deployTxnBlockNumber": "6474236", - "deployTimestamp": "1723307112", + "deployTxnHash": "0x55c6aa35b612646523feefd07b2dc1bf78efb6135f02e52a1cc8aacf26bd0f12", + "deployTxnBlockNumber": "6506106", + "deployTimestamp": "1723741752", "contractName": "Foil", "sourceName": "Foil.sol", - "gasUsed": 428716, - "gasCost": "964291468" + "gasUsed": 428908, + "gasCost": "26803319809" } \ No newline at end of file diff --git a/packages/protocol/deployments/11155111/ReentrancyGuard.json b/packages/protocol/deployments/11155111/ReentrancyGuard.json index c0c527bc..3952de15 100644 --- a/packages/protocol/deployments/11155111/ReentrancyGuard.json +++ b/packages/protocol/deployments/11155111/ReentrancyGuard.json @@ -1,5 +1,5 @@ { - "address": "0xC3aDB913bDB5d8e964dDc9dcCdBE896349DA63E9", + "address": "0x3f5c8BA60B74259Fda75950Ff7d6B60A90852084", "abi": [ { "type": "error", @@ -9,12 +9,12 @@ ], "constructorArgs": [], "linkedLibraries": {}, - "deployTxnHash": "0x7b86f1de0229aa2981a0f891657e7af744f54c9cfad557f5fe0c529a95800f06", - "deployTxnBlockNumber": "6474234", - "deployTimestamp": "1723307088", + "deployTxnHash": "0x7c1160055d38235b86b52108105e1b61d04f5650264b55e8835c69b4b0910eac", + "deployTxnBlockNumber": "6506104", + "deployTimestamp": "1723741728", "sourceName": "node_modules/@openzeppelin/contracts/utils/ReentrancyGuard.sol", "contractName": "ReentrancyGuard", "deployedOn": "deploy.ReentrancyGuard", "gasUsed": 53000, - "gasCost": "1000379233" + "gasCost": "28981752751" } \ No newline at end of file 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/src/contracts/modules/EpochUMASettlementModule.sol b/packages/protocol/src/contracts/modules/EpochUMASettlementModule.sol index 0bea2202..5b275a80 100644 --- a/packages/protocol/src/contracts/modules/EpochUMASettlementModule.sol +++ b/packages/protocol/src/contracts/modules/EpochUMASettlementModule.sol @@ -74,7 +74,7 @@ contract EpochUMASettlementModule is ReentrancyGuard { epoch.params.assertionLiveness, IERC20(epoch.params.bondCurrency), uint64(epoch.params.bondAmount), - bytes32(0), + optimisticOracleV3.defaultIdentifier(), bytes32(0) ); @@ -97,10 +97,12 @@ contract EpochUMASettlementModule is ReentrancyGuard { require(!epoch.settled, "Market already settled"); Epoch.Settlement storage settlement = epoch.settlement; - epoch.settlementPrice = settlement.settlementPrice; - epoch.settled = true; - emit MarketSettled(settlement.settlementPrice); + if(!epoch.settlement.disputed) { + epoch.settlementPrice = settlement.settlementPrice; + epoch.settled = true; + emit MarketSettled(settlement.settlementPrice); + } } function assertionDisputedCallback( @@ -119,4 +121,4 @@ contract EpochUMASettlementModule is ReentrancyGuard { emit SettlementDisputed(block.timestamp); } -} +} \ No newline at end of file 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); + } +}