From 9fa0cdef61ee644d17c14d7d90ee9dab8a7b2ac8 Mon Sep 17 00:00:00 2001 From: Joey <5688912+bachstatter@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:47:44 +1000 Subject: [PATCH] Fill price (#1689) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add more tests for fill price * Refactor fillPrice * Lint * Better order of describe/ before blocks * Keep the calcs in the method. - not sure how to refactor it if I'm not suppose to break it out to methods 🤔 --- .../contracts/storage/AsyncOrder.sol | 23 +++-- .../Market/PerpsMarketModule.test.ts | 94 +++++++++++++++++-- 2 files changed, 98 insertions(+), 19 deletions(-) diff --git a/markets/perps-market/contracts/storage/AsyncOrder.sol b/markets/perps-market/contracts/storage/AsyncOrder.sol index c733450b5d..2f117c30af 100644 --- a/markets/perps-market/contracts/storage/AsyncOrder.sol +++ b/markets/perps-market/contracts/storage/AsyncOrder.sol @@ -285,22 +285,12 @@ library AsyncOrder { return takerFee + makerFee; } - // TODO: refactor possibly function calculateFillPrice( int skew, uint skewScale, int size, uint price ) internal pure returns (uint) { - if (skewScale == 0) { - return price; - } - - int pdBefore = skew.divDecimal(skewScale.toInt()); - int pdAfter = (skew + size).divDecimal(skewScale.toInt()); - int priceBefore = price.toInt() + (price.toInt().mulDecimal(pdBefore)); - int priceAfter = price.toInt() + (price.toInt().mulDecimal(pdAfter)); - // How is the p/d-adjusted price calculated using an example: // // price = $1200 USD (oracle) @@ -328,6 +318,19 @@ library AsyncOrder { // fill_price = (price_before + price_after) / 2 // = (1200 + 1200.12) / 2 // = 1200.06 + if (skewScale == 0) { + return price; + } + // calculate pd (premium/discount) before and after trade + int pdBefore = skew.divDecimal(skewScale.toInt()); + int newSkew = skew + size; + int pdAfter = newSkew.divDecimal(skewScale.toInt()); + + // calculate price before and after trade with pd applied + int priceBefore = price.toInt() + (price.toInt().mulDecimal(pdBefore)); + int priceAfter = price.toInt() + (price.toInt().mulDecimal(pdAfter)); + + // the fill price is the average of those prices return (priceBefore + priceAfter).toUint().divDecimal(DecimalMath.UNIT * 2); } } diff --git a/markets/perps-market/test/integration/Market/PerpsMarketModule.test.ts b/markets/perps-market/test/integration/Market/PerpsMarketModule.test.ts index d0307c5be4..466435953e 100644 --- a/markets/perps-market/test/integration/Market/PerpsMarketModule.test.ts +++ b/markets/perps-market/test/integration/Market/PerpsMarketModule.test.ts @@ -1,6 +1,9 @@ import { ethers } from 'ethers'; import assertBn from '@synthetixio/core-utils/src/utils/assertions/assert-bignumber'; import { bn, bootstrapMarkets } from '../bootstrap'; +import { OpenPositionData, openPosition } from '../helpers'; +import { formatEther } from 'ethers/lib/utils'; +import { snapshotCheckpoint } from '@synthetixio/core-utils/utils/mocha/snapshot'; describe('PerpsMarketModule', () => { const fixture = { @@ -10,7 +13,7 @@ describe('PerpsMarketModule', () => { marketTokenPrice: bn(1000), }; - const { systems, perpsMarkets, restore } = bootstrapMarkets({ + const { systems, perpsMarkets, marketOwner, provider, trader2, keeper } = bootstrapMarkets({ synthMarkets: [ { name: 'Ether', @@ -41,8 +44,6 @@ describe('PerpsMarketModule', () => { }); describe('getMarketSummary', () => { - beforeEach(restore); - it('should return all values successfully', async () => { const summary = await systems().PerpsMarket.getMarketSummary(marketId); assertBn.equal(summary.skew, bn(0)); @@ -53,14 +54,89 @@ describe('PerpsMarketModule', () => { assertBn.equal(summary.indexPrice, fixture.marketTokenPrice); }); }); + describe('fillPrice', () => { - it('should return correct value when passing same as onchain price', async () => { - const price = await systems().PerpsMarket.fillPrice(marketId, bn(1), bn(1000)); - assertBn.equal(price, bn(1000.05)); + let commonOpenPositionProps: Pick< + OpenPositionData, + | 'systems' + | 'provider' + | 'trader' + | 'accountId' + | 'keeper' + | 'marketId' + | 'settlementStrategyId' + >; + before('identify common props', async () => { + commonOpenPositionProps = { + systems, + provider, + marketId: marketId, + trader: trader2(), + accountId: 2, + keeper: keeper(), + settlementStrategyId: bn(0), + }; + }); + + before('add collateral', async () => { + await systems().PerpsMarket.connect(trader2()).modifyCollateral(2, 0, bn(10000000)); + }); + describe('skewScale 0', () => { + const restoreSkewScale = snapshotCheckpoint(provider); + before('set skewScale to 0', async () => { + await systems().PerpsMarket.connect(marketOwner()).setFundingParameters(marketId, 0, 0); + }); + it('should return the index price', async () => { + const price = await systems().PerpsMarket.fillPrice(marketId, bn(1), bn(1000)); + assertBn.equal(price, fixture.marketTokenPrice); + }); + after('restore skewScale', restoreSkewScale); }); - it('should return correct value when passing different price', async () => { - const price = await systems().PerpsMarket.fillPrice(marketId, bn(1), bn(1010)); - assertBn.equal(price, bn(1010.0505)); + + const tests = [ + { + marketSkew: 0, + sizeAndExpectedPrice: [ + { size: 1, price: bn(1010), expectedPrice: bn(1010.0505) }, + { size: -1, price: bn(1010), expectedPrice: bn(1009.9495) }, + ], + }, + { + marketSkew: 10, + sizeAndExpectedPrice: [ + { size: 1, price: bn(1010), expectedPrice: bn(1011.0605) }, + { size: -1, price: bn(1010), expectedPrice: bn(1010.9595) }, + { size: -11, price: bn(1010), expectedPrice: bn(1010.4545) }, + ], + }, + { + marketSkew: -10, + sizeAndExpectedPrice: [ + { size: 1, price: bn(1010), expectedPrice: bn(1009.0405) }, + { size: -1, price: bn(1010), expectedPrice: bn(1008.9395) }, + { size: 11, price: bn(1010), expectedPrice: bn(1009.5455) }, + ], + }, + ]; + tests.forEach(({ marketSkew, sizeAndExpectedPrice }) => { + describe(`marketSkew ${marketSkew}`, () => { + const restoreMarketSkew = snapshotCheckpoint(provider); + before('create market skew', async () => { + if (marketSkew === 0) return; + await openPosition({ + ...commonOpenPositionProps, + sizeDelta: bn(marketSkew), + price: fixture.marketTokenPrice, + }); + }); + sizeAndExpectedPrice.forEach(({ size, price, expectedPrice }) => { + it(`fillPrice for size ${size} and price ${formatEther(price)}`, async () => { + const fillPrice = await systems().PerpsMarket.fillPrice(marketId, bn(size), price); + assertBn.equal(fillPrice, expectedPrice); + }); + }); + after('restore market skew', restoreMarketSkew); + }); }); }); });