diff --git a/.github/workflows/check-packages.yml b/.github/workflows/check-packages.yml index 4617d40b74..8d6a6ae61c 100644 --- a/.github/workflows/check-packages.yml +++ b/.github/workflows/check-packages.yml @@ -1,6 +1,10 @@ name: "Validate yarn.lock and cache" -on: [pull_request] +on: + pull_request: {} + push: + branches: + - "main" jobs: check: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3532636175..8a12915736 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,11 @@ name: Lint -on: [push] + +on: + pull_request: {} + push: + branches: + - "main" + jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/simulate-release.yml b/.github/workflows/simulate-release.yml index 875d1366cd..f08cd1b0cb 100644 --- a/.github/workflows/simulate-release.yml +++ b/.github/workflows/simulate-release.yml @@ -15,6 +15,7 @@ jobs: CANNON_IPFS_URL: "http://127.0.0.1:5001" CANNON_PUBLISH_IPFS_URL: "http://127.0.0.1:5001" strategy: + fail-fast: false matrix: network: [goerli, optimistic-goerli] project: [protocol/oracle-manager, protocol/synthetix] diff --git a/.github/workflows/test-with-coverage.yml b/.github/workflows/test-with-coverage.yml new file mode 100644 index 0000000000..a3ec868cb4 --- /dev/null +++ b/.github/workflows/test-with-coverage.yml @@ -0,0 +1,114 @@ +name: Coverage + +on: + push: + branches: + - "main" + +jobs: + coverage: + runs-on: ubuntu-latest + env: + CANNON_IPFS_URL: "http://127.0.0.1:5001" + CANNON_PUBLISH_IPFS_URL: "http://127.0.0.1:5001" + strategy: + fail-fast: false + matrix: + workspace: [ + "@synthetixio/main", + "@synthetixio/oracle-manager", + # "@synthetixio/governance", # no tests :/ + + "@synthetixio/core-contracts", + "@synthetixio/core-utils", + "@synthetixio/core-modules", + "@synthetixio/hardhat-storage", + "@synthetixio/sample-project", + + # "@synthetixio/legacy-market", # tests fail + "@synthetixio/spot-market", + "@synthetixio/perps-market", + + "@synthetixio/core-subgraph", + "@synthetixio/spot-market-subgraph", + "@synthetixio/perps-market-subgraph", + ] + + include: + - workspace: "@synthetixio/main" + codecov-file: protocol/synthetix/coverage.json + codecov-flags: main + + - workspace: "@synthetixio/oracle-manager" + codecov-file: protocol/oracle-manager/coverage.json + codecov-flags: oracle-manager + + - workspace: "@synthetixio/core-contracts" + codecov-file: utils/core-contracts/coverage.json + codecov-flags: core-contracts + + - workspace: "@synthetixio/core-utils" + codecov-file: utils/core-utils/coverage/lcov.info + codecov-flags: core-utils + + - workspace: "@synthetixio/core-modules" + codecov-file: utils/core-modules/coverage.json + codecov-flags: core-modules + + - workspace: "@synthetixio/hardhat-storage" + codecov-file: utils/hardhat-storage/coverage/lcov.info + codecov-flags: hardhat-storage + + - workspace: "@synthetixio/sample-project" + codecov-file: utils/sample-project/coverage.json + codecov-flags: sample-project + + steps: + - name: Install Foundry (Cannon) + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + - run: anvil -V + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16.20.1" + cache: "yarn" + - uses: ibnesayeed/setup-ipfs@92d412e0dad36c06ffab50733e9c624896a0964f + with: + run_daemon: true + + - run: yarn install --immutable --immutable-cache + - run: yarn workspaces foreach --topological-dev --recursive --verbose --from "${{ matrix.workspace }}" run build:ts + - run: yarn workspaces foreach --topological-dev --recursive --verbose --from "${{ matrix.workspace }}" run build-testable + + - name: Execute size-contracts command + run: | + if yarn workspace ${{ matrix.workspace }} run | grep size-contracts; then + yarn workspace ${{ matrix.workspace }} run size-contracts + else + echo 'SKIP. No "size-contracts" script' + fi + + - name: Check storage.dump.sol is up-to-date + run: | + if yarn workspace ${{ matrix.workspace }} run | grep check:storage; then + yarn workspace ${{ matrix.workspace }} run check:storage + else + echo 'SKIP. No "check:storage" script' + fi + + - name: Execute tests with coverage + run: | + if yarn workspace ${{ matrix.workspace }} run | grep coverage; then + REPORT_GAS=true yarn workspace ${{ matrix.workspace }} run coverage + else + echo 'SKIP. No "coverage" script' + fi + + - if: ${{ matrix.codecov-file }} + name: Upload ${{ matrix.workspace }} coverage to Codecov + uses: codecov/codecov-action@v2 + with: + files: ${{ matrix.codecov-file }} + flags: ${{ matrix.codecov-flags }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 01b734ac9a..ccb1b13388 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,59 +2,36 @@ name: Test on: pull_request: {} - push: - branches: - - "main" jobs: test: runs-on: ubuntu-latest - continue-on-error: true env: CANNON_IPFS_URL: "http://127.0.0.1:5001" CANNON_PUBLISH_IPFS_URL: "http://127.0.0.1:5001" strategy: + fail-fast: false matrix: - package: - [ - core-contracts, - core-utils, - core-modules, - sample-project, - main, - oracle-manager, - spot-market, - perps-market, - core-subgraph, - spot-market-subgraph, - perps-market-subgraph, + workspace: [ + "@synthetixio/main", + "@synthetixio/oracle-manager", + # "@synthetixio/governance", # no tests :/ + + "@synthetixio/core-contracts", + "@synthetixio/core-utils", + "@synthetixio/core-modules", + "@synthetixio/hardhat-storage", + "@synthetixio/sample-project", + + # "@synthetixio/legacy-market", # tests fail + "@synthetixio/spot-market", + "@synthetixio/perps-market", + + "@synthetixio/core-subgraph", + "@synthetixio/spot-market-subgraph", + "@synthetixio/perps-market-subgraph", ] - include: - - package: core-contracts - workspace: "@synthetixio/core-contracts" - coverage-file: utils/core-contracts/coverage.json - - package: core-utils - workspace: "@synthetixio/core-utils" - coverage-file: utils/core-utils/coverage/lcov.info - - package: core-modules - workspace: "@synthetixio/core-modules" - coverage-file: utils/core-modules/coverage.json - - package: sample-project - workspace: "@synthetixio/sample-project" - - package: oracle-manager - workspace: "@synthetixio/oracle-manager" - - package: spot-market - workspace: "@synthetixio/spot-market" - - package: perps-market - workspace: "@synthetixio/perps-market" - - package: main - workspace: "@synthetixio/main" - - package: core-subgraph - workspace: "@synthetixio/core-subgraph" - - package: spot-market-subgraph - workspace: "@synthetixio/spot-market-subgraph" - - package: perps-market-subgraph - workspace: "@synthetixio/perps-market-subgraph" + steps: - name: Install Foundry (Cannon) uses: foundry-rs/foundry-toolchain@v1 @@ -71,10 +48,8 @@ jobs: run_daemon: true - run: yarn install --immutable --immutable-cache - - - run: yarn workspaces foreach --topological-dev --recursive --verbose --from ${{ matrix.workspace }} run build:ts - - - run: yarn workspaces foreach --topological-dev --recursive --verbose --from ${{ matrix.workspace }} run build-testable + - run: yarn workspaces foreach --topological-dev --recursive --verbose --from "${{ matrix.workspace }}" run build:ts + - run: yarn workspaces foreach --topological-dev --recursive --verbose --from "${{ matrix.workspace }}" run build-testable - name: Execute size-contracts command run: | @@ -94,15 +69,8 @@ jobs: - name: Execute tests run: | - if yarn workspace ${{ matrix.workspace }} run | grep coverage; then - REPORT_GAS=true yarn workspace ${{ matrix.workspace }} run coverage + if yarn workspace ${{ matrix.workspace }} run | grep test; then + REPORT_GAS=true yarn workspace ${{ matrix.workspace }} run test else - echo 'SKIP. No "coverage" script' + echo 'SKIP. No "test" script' fi - - - if: ${{ matrix.coverage-file }} - name: Upload ${{ matrix.workspace }} coverage to Codecov - uses: codecov/codecov-action@v2 - with: - files: ${{ matrix.coverage-file }} - flags: ${{ matrix.package }} diff --git a/markets/perps-market/contracts/interfaces/IAsyncOrderModule.sol b/markets/perps-market/contracts/interfaces/IAsyncOrderModule.sol index 3e572bb14b..b8770cedaf 100644 --- a/markets/perps-market/contracts/interfaces/IAsyncOrderModule.sol +++ b/markets/perps-market/contracts/interfaces/IAsyncOrderModule.sol @@ -32,6 +32,24 @@ interface IAsyncOrderModule { address sender ); + /** + * @notice Gets fired when a new order is committed while a previous one was expired. + * @param marketId Id of the market used for the trade. + * @param accountId Id of the account used for the trade. + * @param sizeDelta requested change in size of the order sent by the user. + * @param acceptablePrice maximum or minimum, depending on the sizeDelta direction, accepted price to settle the order, set by the user. + * @param settlementTime Time at which the order can be settled. + * @param trackingCode Optional code for integrator tracking purposes. + */ + event PreviousOrderExpired( + uint128 indexed marketId, + uint128 indexed accountId, + int128 sizeDelta, + uint256 acceptablePrice, + uint256 settlementTime, + bytes32 indexed trackingCode + ); + /** * @notice Commit an async order via this function * @param commitment Order commitment data (see AsyncOrder.OrderCommitmentRequest struct). @@ -55,9 +73,24 @@ interface IAsyncOrderModule { * @param marketId id of the market. * @param sizeDelta size of position. * @return orderFees incurred fees. + * @return fillPrice price at which the order would be filled. */ function computeOrderFees( uint128 marketId, int128 sizeDelta - ) external view returns (uint256 orderFees); + ) external view returns (uint256 orderFees, uint256 fillPrice); + + /** + * @notice For a given market, account id, and a position size, returns the required total account margin for this order to succeed + * @dev Useful for integrators to determine if an order will succeed or fail + * @param marketId id of the market. + * @param accountId id of the trader account. + * @param sizeDelta size of position. + * @return requiredMargin margin required for the order to succeed. + */ + function requiredMarginForOrder( + uint128 marketId, + uint128 accountId, + int128 sizeDelta + ) external view returns (uint256 requiredMargin); } diff --git a/markets/perps-market/contracts/modules/AsyncOrderModule.sol b/markets/perps-market/contracts/modules/AsyncOrderModule.sol index 84af91bf50..c855acb6bd 100644 --- a/markets/perps-market/contracts/modules/AsyncOrderModule.sol +++ b/markets/perps-market/contracts/modules/AsyncOrderModule.sol @@ -7,7 +7,9 @@ import {Account} from "@synthetixio/main/contracts/storage/Account.sol"; import {AccountRBAC} from "@synthetixio/main/contracts/storage/AccountRBAC.sol"; import {IAsyncOrderModule} from "../interfaces/IAsyncOrderModule.sol"; import {PerpsMarket} from "../storage/PerpsMarket.sol"; +import {PerpsAccount} from "../storage/PerpsAccount.sol"; import {AsyncOrder} from "../storage/AsyncOrder.sol"; +import {Position} from "../storage/Position.sol"; import {PerpsPrice} from "../storage/PerpsPrice.sol"; import {GlobalPerpsMarket} from "../storage/GlobalPerpsMarket.sol"; import {PerpsMarketConfiguration} from "../storage/PerpsMarketConfiguration.sol"; @@ -24,6 +26,7 @@ contract AsyncOrderModule is IAsyncOrderModule { using PerpsPrice for PerpsPrice.Data; using PerpsMarket for PerpsMarket.Data; using AsyncOrder for AsyncOrder.Data; + using PerpsAccount for PerpsAccount.Data; using SettlementStrategy for SettlementStrategy.Data; using GlobalPerpsMarket for GlobalPerpsMarket.Data; using PerpsMarketConfiguration for PerpsMarketConfiguration.Data; @@ -52,7 +55,26 @@ contract AsyncOrderModule is IAsyncOrderModule { SettlementStrategy.Data storage strategy = PerpsMarketConfiguration .loadValidSettlementStrategy(commitment.marketId, commitment.settlementStrategyId); - AsyncOrder.Data storage order = AsyncOrder.createValid(commitment, strategy); + // pull order from storage and check if it is valid (not pending unexpired order) + AsyncOrder.Data storage order = AsyncOrder.checkPendingOrder(commitment.accountId); + + // if order (previous) sizeDelta is not zero and didn't revert while checking, it means the previous order expired + if (order.request.sizeDelta != 0) { + // @notice not including the expiration time since it requires the previous settlement strategy to be loaded and enabled, otherwise loading it will revert and will prevent new orders to be committed + emit PreviousOrderExpired( + order.request.marketId, + order.request.accountId, + order.request.sizeDelta, + order.request.acceptablePrice, + order.settlementTime, + order.request.trackingCode + ); + } + + // Replace previous (or empty) order with the commitment request + order.settlementTime = block.timestamp + strategy.settlementDelay; + order.request = commitment; + (, uint feesAccrued, , ) = order.validateRequest( strategy, PerpsPrice.getCurrentPrice(commitment.marketId) @@ -89,13 +111,49 @@ contract AsyncOrderModule is IAsyncOrderModule { function computeOrderFees( uint128 marketId, int128 sizeDelta - ) external view override returns (uint256 orderFees) { - PerpsMarket.Data storage perpsMarket = PerpsMarket.load(marketId); - int256 skew = perpsMarket.skew; + ) external view override returns (uint256 orderFees, uint256 fillPrice) { + (orderFees, fillPrice) = _computeOrderFees(marketId, sizeDelta); + } + + function requiredMarginForOrder( + uint128 accountId, + uint128 marketId, + int128 sizeDelta + ) external view override returns (uint256 requiredMargin) { + PerpsMarketConfiguration.Data storage marketConfig = PerpsMarketConfiguration.load( + marketId + ); + + Position.Data storage oldPosition = PerpsMarket.accountPosition(marketId, accountId); + ( + , + uint256 currentMaintenanceMargin, + uint256 currentTotalLiquidationRewards, + + ) = PerpsAccount.load(accountId).getAccountRequiredMargins(); + (uint256 orderFees, uint256 fillPrice) = _computeOrderFees(marketId, sizeDelta); + + return + AsyncOrder.getRequiredMarginWithNewPosition( + marketConfig, + marketId, + oldPosition.size, + oldPosition.size + sizeDelta, + fillPrice, + currentMaintenanceMargin, + currentTotalLiquidationRewards + ) + orderFees; + } + + function _computeOrderFees( + uint128 marketId, + int128 sizeDelta + ) private view returns (uint256 orderFees, uint256 fillPrice) { + int256 skew = PerpsMarket.load(marketId).skew; PerpsMarketConfiguration.Data storage marketConfig = PerpsMarketConfiguration.load( marketId ); - uint256 fillPrice = AsyncOrder.calculateFillPrice( + fillPrice = AsyncOrder.calculateFillPrice( skew, marketConfig.skewScale, sizeDelta, diff --git a/markets/perps-market/contracts/storage/AsyncOrder.sol b/markets/perps-market/contracts/storage/AsyncOrder.sol index 80fb2aaf53..ab9b84ccfb 100644 --- a/markets/perps-market/contracts/storage/AsyncOrder.sol +++ b/markets/perps-market/contracts/storage/AsyncOrder.sol @@ -151,23 +151,9 @@ library AsyncOrder { checkWithinSettlementWindow(order, strategy); } - /** - * @dev Reverts if the order does not belongs to the market or not exists. Otherwise, returns the order. - * @dev non-existent order is considered an order with sizeDelta == 0. - */ - function createValid( - OrderCommitmentRequest memory newRequest, - SettlementStrategy.Data storage strategy - ) internal returns (Data storage order) { - order = checkPendingOrder(newRequest.accountId); - - order.settlementTime = block.timestamp + strategy.settlementDelay; - order.request = newRequest; - } - /** * @dev Reverts if there is a pending order. - * @dev A pending order is one that has a sizeDelta or isn't expired yet. + * @dev A pending order is one that has a sizeDelta and isn't expired yet. */ function checkPendingOrder(uint128 accountId) internal view returns (Data storage order) { order = load(accountId); @@ -328,7 +314,7 @@ library AsyncOrder { revert InsufficientMargin(runtime.currentAvailableMargin, runtime.orderFees); } - oldPosition = PerpsMarket.load(runtime.marketId).positions[runtime.accountId]; + oldPosition = PerpsMarket.accountPosition(runtime.marketId, runtime.accountId); PerpsMarket.validatePositionSize( perpsMarketData, @@ -339,7 +325,7 @@ library AsyncOrder { runtime.newPositionSize = oldPosition.size + runtime.sizeDelta; runtime.totalRequiredMargin = - _getRequiredMarginWithNewPosition( + getRequiredMarginWithNewPosition( marketConfig, runtime.marketId, oldPosition.size, @@ -466,7 +452,7 @@ library AsyncOrder { * @notice After the required margins are calculated with the old position, this function replaces the * old position data with the new position margin requirements and returns them. */ - function _getRequiredMarginWithNewPosition( + function getRequiredMarginWithNewPosition( PerpsMarketConfiguration.Data storage marketConfig, uint128 marketId, int128 oldPositionSize, diff --git a/markets/perps-market/contracts/storage/PerpsMarket.sol b/markets/perps-market/contracts/storage/PerpsMarket.sol index dc5934c15e..31e2a6ac23 100644 --- a/markets/perps-market/contracts/storage/PerpsMarket.sol +++ b/markets/perps-market/contracts/storage/PerpsMarket.sol @@ -310,4 +310,11 @@ library PerpsMarket { return traderUnrealizedPnl + unrealizedFunding - self.debtCorrectionAccumulator; } + + function accountPosition( + uint128 marketId, + uint128 accountId + ) internal view returns (Position.Data storage position) { + position = load(marketId).positions[accountId]; + } } diff --git a/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.commit.test.ts b/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.commit.test.ts index 2ac5307c6d..aa6b59cc4f 100644 --- a/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.commit.test.ts +++ b/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.commit.test.ts @@ -229,6 +229,8 @@ describe('Commit Offchain Async Order test', () => { startTime = await getTxTime(provider(), tx); }); + const restoreToSettle = snapshotCheckpoint(provider); + it('emit event', async () => { await assertEvent( tx, @@ -270,6 +272,7 @@ describe('Commit Offchain Async Order test', () => { }); describe('can settle order', () => { + before(restoreToSettle); before('settle', async () => { const settlementTime = startTime + DEFAULT_SETTLEMENT_STRATEGY.settlementDelay + 1; await fastForwardTo(settlementTime, provider()); @@ -330,6 +333,74 @@ describe('Commit Offchain Async Order test', () => { }); }); }); + + describe('when order expired', () => { + let expirationTime: number; + + before('move after expiration', async () => { + expirationTime = + startTime + + DEFAULT_SETTLEMENT_STRATEGY.settlementDelay + + DEFAULT_SETTLEMENT_STRATEGY.settlementWindowDuration + + 1; + await fastForwardTo(expirationTime, provider()); + }); + + it('reverts if attempt to settle', async () => { + await assertRevert( + settleOrder({ + systems, + keeper: keeper(), + accountId: 2, + feedId: DEFAULT_SETTLEMENT_STRATEGY.feedId, + settlementTime: expirationTime, + offChainPrice: 1000, + }), + 'SettlementWindowExpired' + ); + }); + + describe('can commit another order after expiration', () => { + let tx: ethers.ContractTransaction; + let secondOrderStartTime: number; + before('commit the order', async () => { + tx = await systems() + .PerpsMarket.connect(trader1()) + .commitOrder({ + marketId: ethMarketId, + accountId: 2, + sizeDelta: bn(1), + settlementStrategyId: 0, + acceptablePrice: bn(1050), // 5% slippage + referrer: ethers.constants.AddressZero, + trackingCode: ethers.constants.HashZero, + }); + secondOrderStartTime = await getTxTime(provider(), tx); + }); + + it('emit the order commited event', async () => { + await assertEvent( + tx, + `OrderCommitted(${ethMarketId}, 2, ${DEFAULT_SETTLEMENT_STRATEGY.strategyType}, ${bn( + 1 + )}, ${bn(1050)}, ${secondOrderStartTime + 5}, ${secondOrderStartTime + 5 + 120}, "${ + ethers.constants.HashZero + }", "${await trader1().getAddress()}")`, + systems().PerpsMarket + ); + }); + + it('emit the order expired event', async () => { + await assertEvent( + tx, + `PreviousOrderExpired(${ethMarketId}, 2, ${bn(1)}, ${bn(1050)}, ${startTime + 5}, "${ + ethers.constants.HashZero + }")`, + systems().PerpsMarket + ); + }); + }); + }); }); } }); diff --git a/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.fees.test.ts b/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.fees.test.ts index a5ef78bece..241b621da0 100644 --- a/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.fees.test.ts +++ b/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.fees.test.ts @@ -155,10 +155,8 @@ describe('Offchain Async Order test - fees', () => { }); it('returns proper fees on getOrderFees', async () => { - assertBn.equal( - await systems().PerpsMarket.computeOrderFees(ethMarketId, sizeDelta), - feesPaidOnSettle.perpsMarketFee - ); + const [orderFees] = await systems().PerpsMarket.computeOrderFees(ethMarketId, sizeDelta); + assertBn.equal(orderFees, feesPaidOnSettle.perpsMarketFee); }); it('validate that not fees are paid on commit', async () => { diff --git a/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.referrer.test.ts b/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.referrer.test.ts index 7ea6ffb480..a452232c2f 100644 --- a/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.referrer.test.ts +++ b/markets/perps-market/test/integration/Orders/OffchainAsyncOrder.referrer.test.ts @@ -73,9 +73,11 @@ describe('OffchainAsyncOrder - feeCollector - referrer', () => { before('identify data', async () => { beforeWithdrawableUsd = wei(await systems().Core.getWithdrawableMarketUsd(superMarketId())); // NOTE: expected fees here does not include settlement reward - expectedFees = wei( - await systems().PerpsMarket.computeOrderFees(perpsMarkets()[0].marketId(), sizeDelta) + const [fees] = await systems().PerpsMarket.computeOrderFees( + perpsMarkets()[0].marketId(), + sizeDelta ); + expectedFees = wei(fees); expectedToReferrer = expectedFees.mul(referrerRatio); expectedToFeeCollector = expectedFees.sub(expectedToReferrer).mul(feeCollectorRatio); }); @@ -135,9 +137,11 @@ describe('OffchainAsyncOrder - feeCollector - referrer', () => { before('identify data', async () => { previousReferrerBalance = wei(await systems().USD.balanceOf(await referrer.getAddress())); // NOTE: expected fees here does not include settlement reward - const expectedFees = wei( - await systems().PerpsMarket.computeOrderFees(perpsMarkets()[0].marketId(), sizeDelta) + const [fees] = await systems().PerpsMarket.computeOrderFees( + perpsMarkets()[0].marketId(), + sizeDelta ); + const expectedFees = wei(fees); const currentFeeCollectorBalance = wei( await systems().USD.balanceOf(systems().FeeCollectorMock.address) ); @@ -185,10 +189,12 @@ describe('OffchainAsyncOrder - feeCollector - referrer', () => { previousFeeCollectorBalance = wei( await systems().USD.balanceOf(systems().FeeCollectorMock.address) ); - // NOTE: expected fees here does not include settlement reward - const expectedFees = wei( - await systems().PerpsMarket.computeOrderFees(perpsMarkets()[0].marketId(), sizeDelta) + const [orderFees] = await systems().PerpsMarket.computeOrderFees( + perpsMarkets()[0].marketId(), + sizeDelta ); + // NOTE: expected fees here does not include settlement reward + const expectedFees = wei(orderFees); const currentReferrerBalance = wei( await systems().USD.balanceOf(await referrer.getAddress()) ); @@ -233,10 +239,12 @@ describe('OffchainAsyncOrder - feeCollector - referrer', () => { let expectedToReferrer: Wei, expectedToFeeCollector: Wei; const sizeDelta = bn(25); before('identify data', async () => { - // NOTE: expected fees here does not include settlement reward - const expectedFees = wei( - await systems().PerpsMarket.computeOrderFees(perpsMarkets()[0].marketId(), sizeDelta) + const [orderFees] = await systems().PerpsMarket.computeOrderFees( + perpsMarkets()[0].marketId(), + sizeDelta ); + // NOTE: expected fees here does not include settlement reward + const expectedFees = wei(orderFees); const currentFeeCollectorBalance = wei( await systems().USD.balanceOf(systems().FeeCollectorMock.address) ); @@ -292,9 +300,11 @@ describe('OffchainAsyncOrder - feeCollector - referrer', () => { await systems().USD.balanceOf(systems().FeeCollectorMock.address) ); // NOTE: expected fees here does not include settlement reward - const expectedFees = wei( - await systems().PerpsMarket.computeOrderFees(perpsMarkets()[0].marketId(), sizeDelta) + const [fees] = await systems().PerpsMarket.computeOrderFees( + perpsMarkets()[0].marketId(), + sizeDelta ); + const expectedFees = wei(fees); const currentReferrerBalance = wei( await systems().USD.balanceOf(await referrer.getAddress()) ); diff --git a/markets/perps-market/test/integration/Orders/Order.marginValidation.test.ts b/markets/perps-market/test/integration/Orders/Order.marginValidation.test.ts index f615031dcb..ce22719c69 100644 --- a/markets/perps-market/test/integration/Orders/Order.marginValidation.test.ts +++ b/markets/perps-market/test/integration/Orders/Order.marginValidation.test.ts @@ -82,7 +82,7 @@ describe('Orders - margin validation', () => { describe('openPosition 1 failure', () => { let orderFees: ethers.BigNumber; before('get order fees', async () => { - orderFees = await systems().PerpsMarket.computeOrderFees(51, 3); + [orderFees] = await systems().PerpsMarket.computeOrderFees(51, 3); }); it('reverts if not enough margin', async () => { @@ -98,6 +98,15 @@ describe('Orders - margin validation', () => { wei(10_000) ); + const totalRequiredMargin = initialMargin + .add(getMaxLiquidationReward(liquidationMargin, wei(100), wei(500))) + .add(orderFees); + + assertBn.equal( + await systems().PerpsMarket.requiredMarginForOrder(2, 51, bn(3)), + totalRequiredMargin.toBN() + ); + await assertRevert( systems() .PerpsMarket.connect(trader1()) @@ -110,10 +119,7 @@ describe('Orders - margin validation', () => { referrer: ethers.constants.AddressZero, trackingCode: ethers.constants.HashZero, }), - `InsufficientMargin("${bn(100)}", "${initialMargin - .add(getMaxLiquidationReward(liquidationMargin, wei(100), wei(500))) - .add(orderFees) - .toString(18, true)}")` + `InsufficientMargin("${bn(100)}", "${totalRequiredMargin.toString(18, true)}")` ); }); }); @@ -149,7 +155,7 @@ describe('Orders - margin validation', () => { describe('openPosition 2 failure', () => { let orderFees: ethers.BigNumber; before('get order fees', async () => { - orderFees = await systems().PerpsMarket.computeOrderFees(50, 5); + [orderFees] = await systems().PerpsMarket.computeOrderFees(50, 5); }); it('reverts if not enough margin', async () => { @@ -186,6 +192,11 @@ describe('Orders - margin validation', () => { .add(liqReward) .add(orderFees); + assertBn.equal( + await systems().PerpsMarket.requiredMarginForOrder(2, 50, bn(5)), + totalRequiredMargin.toBN() + ); + await assertRevert( systems() .PerpsMarket.connect(trader1()) @@ -233,7 +244,7 @@ describe('Orders - margin validation', () => { describe('modify position', () => { let orderFees: ethers.BigNumber; before('get order fees', async () => { - orderFees = await systems().PerpsMarket.computeOrderFees(50, 5); + [orderFees] = await systems().PerpsMarket.computeOrderFees(50, 5); }); it('reverts if not enough margin', async () => { @@ -270,6 +281,11 @@ describe('Orders - margin validation', () => { .add(liqReward) .add(orderFees); + assertBn.equal( + await systems().PerpsMarket.requiredMarginForOrder(2, 50, bn(5)), + totalRequiredMargin.toBN() + ); + await assertRevert( systems() .PerpsMarket.connect(trader1()) diff --git a/markets/perps-market/test/integration/Orders/GetOrderFees.test.ts b/markets/perps-market/test/integration/helpers/maxSize.ts similarity index 100% rename from markets/perps-market/test/integration/Orders/GetOrderFees.test.ts rename to markets/perps-market/test/integration/helpers/maxSize.ts