Empty orders do not request from oracle and during settlement they use an invalid oracle version with `price=0` which messes up a lot of fees and funding accounting leading to loss of funds for the makers
When `market.update` which doesn't change user's position is called, a new (current) global order is created, but the oracle version is not requested due to empty order. This means that during the order settlement, it will use non-existant invalid oracle version with `price = 0`. This price is then used to accumulate all the data in this invalid `Version`, meaning accounting is done using `price = 0`, which is totally incorrect. For instance, all funding and fees calculations multiply by oracle version's price, thus all time periods between empty order and the next valid oracle version will not accumulate any fees, which is funds usually lost by makers (as makers won't receive fees/funding for the risk they take).\\nWhen `market.update` is called, it requests a new oracle version at the current order's timestamp unless the order is empty:\\n```\\n// request version\\nif (!newOrder.isEmpty()) oracle.request(IMarket(this), account);\\n```\\n\\nThe order is empty when it doesn't modify user position:\\n```\\nfunction isEmpty(Order memory self) internal pure returns (bool) {\\n    return pos(self).isZero() && neg(self).isZero();\\n}\\n\\nfunction pos(Order memory self) internal pure returns (UFixed6) {\\n    return self.makerPos.add(self.longPos).add(self.shortPos);\\n}\\n\\nfunction neg(Order memory self) internal pure returns (UFixed6) {\\n    return self.makerNeg.add(self.longNeg).add(self.shortNeg);\\n}\\n```\\n\\nLater, when a valid oracle version is commited, during the settlement process, oracle version at the position is used:\\n```\\nfunction _processOrderGlobal(\\n    Context memory context,\\n    SettlementContext memory settlementContext,\\n    uint256 newOrderId,\\n    Order memory newOrder\\n) private {\\n    // @audit no oracle version at this timestamp, thus it's invalid with `price=0`\\n    OracleVersion memory oracleVersion = oracle.at(newOrder.timestamp); \\n\\n    context.pending.global.sub(newOrder);\\n    // @audit order is invalidated (it's already empty anyway), but the `price=0` is still used everywhere\\n    if (!oracleVersion.valid) newOrder.invalidate();\\n\\n    VersionAccumulationResult memory accumulationResult;\\n    (settlementContext.latestVersion, context.global, accumulationResult) = VersionLib.accumulate(\\n        settlementContext.latestVersion,\\n        context.global,\\n        context.latestPosition.global,\\n        newOrder,\\n        settlementContext.orderOracleVersion,\\n        oracleVersion, // @audit <<< when oracleVersion is invalid, the `price=0` will still be used here\\n        context.marketParameter,\\n        context.riskParameter\\n    );\\n// rest of code\\n```\\n\\nIf the oracle version is invalid, the order is invalidated, but the `price=0` is still used to accumulate. It doesn't affect pnl from price move, because the final oracle version is always valid, thus the correct price is used to evaluate all possible account actions, however it does affect accumulated fees and funding:\\n```\\nfunction _accumulateLinearFee(\\n    Version memory next,\\n    AccumulationContext memory context,\\n    VersionAccumulationResult memory result\\n) private pure {\\n    (UFixed6 makerLinearFee, UFixed6 makerSubtractiveFee) = _accumulateSubtractiveFee(\\n        context.riskParameter.makerFee.linear(\\n            Fixed6Lib.from(context.order.makerTotal()),\\n            context.toOracleVersion.price.abs() // @audit <<< price == 0 for invalid oracle version\\n        ),\\n        context.order.makerTotal(),\\n        context.order.makerReferral,\\n        next.makerLinearFee\\n    );\\n// rest of code\\n    // Compute long-short funding rate\\n    Fixed6 funding = context.global.pAccumulator.accumulate(\\n        context.riskParameter.pController,\\n        toSkew.unsafeDiv(Fixed6Lib.from(context.riskParameter.takerFee.scale)).min(Fixed6Lib.ONE).max(Fixed6Lib.NEG_ONE),\\n        context.fromOracleVersion.timestamp,\\n        context.toOracleVersion.timestamp,\\n        context.fromPosition.takerSocialized().mul(context.fromOracleVersion.price.abs()) // @audit <<< price == 0 for invalid oracle version\\n    );\\n// rest of code\\nfunction _accumulateInterest(\\n    Version memory next,\\n    AccumulationContext memory context\\n) private pure returns (Fixed6 interestMaker, Fixed6 interestLong, Fixed6 interestShort, UFixed6 interestFee) {\\n    // @audit price = 0 and notional = 0 for invalid oracle version\\n    UFixed6 notional = context.fromPosition.long.add(context.fromPosition.short).min(context.fromPosition.maker).mul(context.fromOracleVersion.price.abs());\\n// rest of code\\n```\\n\\nAs can be seen, all funding and fees accumulations multiply by oracle version's price (which is 0), thus during these time intervals fees and funding are 0.\\nThis will happen by itself during any period when there are no orders, because oracle provider's settlement callback uses `market.update` with empty order to settle user account, thus any non-empty order is always followed by an empty order for the next version and `price = 0` will be used to settle it until the next non-empty order:\\n```\\nfunction _settle(IMarket market, address account) private {\\n    market.update(account, UFixed6Lib.MAX, UFixed6Lib.MAX, UFixed6Lib.MAX, Fixed6Lib.ZERO, false);\\n}\\n```\\n\\nThe scenario above is demonstrated in the test, add this to test/unit/market/Market.test.ts:\\n```\\nit('no fees accumulation due to invalid version with price = 0', async () => {\\n\\nfunction setupOracle(price: string, timestamp : number, nextTimestamp : number) {\\n    const oracleVersion = {\\n    price: parse6decimal(price),\\n    timestamp: timestamp,\\n    valid: true,\\n    }\\n    oracle.at.whenCalledWith(oracleVersion.timestamp).returns(oracleVersion)\\n    oracle.status.returns([oracleVersion, nextTimestamp])\\n    oracle.request.returns()\\n}\\n\\nfunction setupOracleAt(price: string, valid : boolean, timestamp : number) {\\n    const oracleVersion = {\\n    price: parse6decimal(price),\\n    timestamp: timestamp,\\n    valid: valid,\\n    }\\n    oracle.at.whenCalledWith(oracleVersion.timestamp).returns(oracleVersion)\\n}\\n\\nconst riskParameter = { // rest of code(await market.riskParameter()) }\\nconst riskParameterMakerFee = { // rest of coderiskParameter.makerFee }\\nriskParameterMakerFee.linearFee = parse6decimal('0.005')\\nriskParameterMakerFee.proportionalFee = parse6decimal('0.0025')\\nriskParameterMakerFee.adiabaticFee = parse6decimal('0.01')\\nriskParameter.makerFee = riskParameterMakerFee\\nconst riskParameterTakerFee = { // rest of coderiskParameter.takerFee }\\nriskParameterTakerFee.linearFee = parse6decimal('0.005')\\nriskParameterTakerFee.proportionalFee = parse6decimal('0.0025')\\nriskParameterTakerFee.adiabaticFee = parse6decimal('0.01')\\nriskParameter.takerFee = riskParameterTakerFee\\nawait market.connect(owner).updateRiskParameter(riskParameter)\\n\\ndsu.transferFrom.whenCalledWith(user.address, market.address, COLLATERAL.mul(1e12)).returns(true)\\ndsu.transferFrom.whenCalledWith(userB.address, market.address, COLLATERAL.mul(1e12)).returns(true)\\n\\nsetupOracle('100', TIMESTAMP, TIMESTAMP + 100);\\n\\nawait market\\n    .connect(user)\\n    ['update(address,uint256,uint256,uint256,int256,bool)'](user.address, POSITION, 0, 0, COLLATERAL, false);\\nawait market\\n    .connect(userB)\\n    ['update(address,uint256,uint256,uint256,int256,bool)'](userB.address, 0, POSITION, 0, COLLATERAL, false);\\n\\nsetupOracle('100', TIMESTAMP + 100, TIMESTAMP + 200);\\nawait market\\n    .connect(user)\\n    ['update(address,uint256,uint256,uint256,int256,bool)'](user.address, POSITION, 0, 0, 0, false);\\n\\n// oracle is commited at timestamp+200\\nsetupOracle('100', TIMESTAMP + 200, TIMESTAMP + 300);\\nawait market\\n    .connect(user)\\n    ['update(address,uint256,uint256,uint256,int256,bool)'](user.address, POSITION, 0, 0, 0, false);\\n\\n// oracle is not commited at timestamp+300\\nsetupOracle('100', TIMESTAMP + 200, TIMESTAMP + 400);\\nsetupOracleAt('0', false, TIMESTAMP + 300);\\nawait market\\n    .connect(user)\\n    ['update(address,uint256,uint256,uint256,int256,bool)'](user.address, POSITION, 0, 0, 0, false);\\n\\n// settle to see makerValue at all versions\\nsetupOracle('100', TIMESTAMP + 400, TIMESTAMP + 500);\\n\\nawait market.settle(user.address);\\nawait market.settle(userB.address);\\n\\nvar ver = await market.versions(TIMESTAMP + 200);\\nconsole.log("version 200: longValue: " + ver.longValue + " makerValue: " + ver.makerValue);\\nvar ver = await market.versions(TIMESTAMP + 300);\\nconsole.log("version 300: longValue: " + ver.longValue + " makerValue: " + ver.makerValue);\\nvar ver = await market.versions(TIMESTAMP + 400);\\nconsole.log("version 400: longValue: " + ver.longValue + " makerValue: " + ver.makerValue);\\n})\\n```\\n\\nConsole log:\\n```\\nversion 200: longValue: -318 makerValue: 285\\nversion 300: longValue: -100000637 makerValue: 100500571\\nversion 400: longValue: -637 makerValue: 571\\n```\\n\\nNotice, that fees are accumulated between versions 200 and 300, version 300 has huge pnl (because it's evaluated at price = 0), which then returns to normal at version 400, but no fees are accumulated between version 300 and 400 due to version 300 having `price = 0`.
Keep the price from the previous valid oracle version and use it instead of oracle version's one if oracle version's price == 0.
All fees and funding are incorrectly calculated as 0 during any period when there are no non-empty orders (which will be substantially more than 50% of the time, more like 90% of the time). Since most fees and funding are received by makers as a compensation for their price risk, this means makers will lose all these under-calculated fees and will receive a lot less fees and funding than expected.
```\\n// request version\\nif (!newOrder.isEmpty()) oracle.request(IMarket(this), account);\\n```\\n