Makers can lose funds from price movement even when no long and short positions are opened, due to incorrect distribution of adiabatic fees exposure between makers
Adiabatic fees introduced in this new update of the protocol (v2.3) were introduced to solve the problem of adiabatic fees netting out to 0 in market token's rather than in USD terms. With the new versions, this problem is solved and adiabatic fees now net out to 0 in USD terms. However, they net out to 0 only for the whole makers pool, but each individual maker can have profit or loss from adiabatic fees at different price levels all else being equal. This creates unexpected risk of loss of funds from adiabatic fees for individual makers, which can be significant, up to several percents of the amount invested.\\nThe issue is demonstrated in the following scenario:\\n`price = 1`\\nAlice open `maker = 10` (collateral = +0.9 from adiabatic fee)\\nBob opens `maker = 10` (collateral = +0.7 from adiabatic fee)\\nPath A. `price = 1`. Bob closes (final collateral = +0), Alice closes (final collaterral = +0)\\nPath B. `price = 2`. Bob closes (final collateral = +0.1), Alice closes (final collaterral = -0.1)\\nPath C. `price = 0.5`. Bob closes (final collateral = -0.05), Alice closes (final collateral = +0.05)\\nNotice that both Alice and Bob are the only makers, there are 0 longs and 0 shorts, but still both Alice and Bob pnl depends on the market price due to pnl from adiabatic fees. Adiabatic fees net out to 0 for all makers aggregated (Alice + Bob), but not for individual makers. Individual makers pnl from adiabatic fees is more or less random depending on the other makers who have opened.\\nIf Alice were the only maker, then:\\nprice = 1\\nAlice opens `maker = 10` (collateral = +0.9)\\nprice = 2: exposure adjusted +0.9 (Alice collateral = +1.8)\\nAlice closes `maker = 10` (adiabatic fees = `-1.8`, Alice final collateral = 0)\\nFor the lone maker there is no such problem, final collateral is 0 regardless of price. The core of the issue lies in the fact that the maker's adiabatic fees exposure adjustment is weighted by makers open maker amount. So in the first example:\\nprice = 1. Alice `maker = 10, exposure = +0.9`, Bob `maker = 10, exposure = +0.7`\\nprice = 2. Total exposure is adjusted by +1.6, split evenly between Alice and Bob (+0.8 for each)\\nAlice new exposure = 0.9 + 0.8 = +1.7 (but adiabatic fees paid to close = -1.8)\\nBob new exposure = 0.7 + 0.8 = +1.5 (but adiabatic fees paid to close = -1.4)\\nIf maker exposure adjustment was weighted by individual makers exposure, then all is correct:\\nprice = 1. Alice `maker = 10, exposure = +0.9`, Bob `maker = 10, exposure = +0.7`\\nprice = 2. Total exposure is adjusted by +1.6, split 0.9:0.7 between Alice and Bob, e.g. +0.9 for Alice, +0.7 for Bob\\nAlice new exposure = 0.9 + 0.9 = +1.8 (adiabatic fees paid to close = -1.8, net out to 0)\\nBob new exposure = 0.7 + 0.7 = +1.4 (adiabatic fees paid to close = -1.4, net out to 0)\\nIn the worst case, in the example above, if Bob opens `maker = 40` (adiabatic fees scale = 50), then at `price = 2`, Alice's final collateral is `-0.4` due to adiabatic fees. Given that Alice's position is 10 at `price = 2` (notional = 20), a loss of `-0.4` is a loss of `-2%` at 1x leverage, which is quite significant.\\nThe scenario above is demonstrated in the test, change the following test in test/unit/market/Market.test.ts:\\n```\\nit('adiabatic fee', async () => {\\n  function 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\\n  async function showInfo() {\\n    await market.settle(user.address);\\n    await market.settle(userB.address);\\n    await market.settle(userC.address);\\n    var sum : BigNumber = BigNumber.from('0');\\n    var info = await market.locals(user.address);\\n    console.log("user collateral = " + info.collateral);\\n    sum = sum.add(info.collateral);\\n    var info = await market.locals(userB.address);\\n    sum = sum.add(info.collateral);\\n    console.log("userB collateral = " + info.collateral);\\n    var info = await market.locals(userC.address);\\n    sum = sum.add(info.collateral);\\n  }\\n\\n  async function showVer(ver : number) {\\n    var v = await market.versions(ver);\\n    console.log("ver" + ver + ": makerValue=" + v.makerValue + " longValue=" + v.longValue + \\n    " makerPosFee=" + v.makerPosFee + " makerNegFee=" + v.makerNegFee +\\n    " takerPosFee=" + v.takerPosFee + " takerNegFee=" + v.takerNegFee\\n    );\\n  }\\n\\n  const riskParameter = { // rest of code(await market.riskParameter()) }\\n  const riskParameterMakerFee = { // rest of coderiskParameter.makerFee }\\n  riskParameterMakerFee.linearFee = parse6decimal('0.00')\\n  riskParameterMakerFee.proportionalFee = parse6decimal('0.00')\\n  riskParameterMakerFee.adiabaticFee = parse6decimal('0.01')\\n  riskParameterMakerFee.scale = parse6decimal('50.0')\\n  riskParameter.makerFee = riskParameterMakerFee\\n  const riskParameterTakerFee = { // rest of coderiskParameter.takerFee }\\n  riskParameterTakerFee.linearFee = parse6decimal('0.00')\\n  riskParameterTakerFee.proportionalFee = parse6decimal('0.00')\\n  riskParameterTakerFee.adiabaticFee = parse6decimal('0.01')\\n  riskParameterTakerFee.scale = parse6decimal('50.0')\\n  riskParameter.takerFee = riskParameterTakerFee\\n  await market.connect(owner).updateRiskParameter(riskParameter)\\n\\n  marketParameter = {\\n    fundingFee: parse6decimal('0.0'),\\n    interestFee: parse6decimal('0.0'),\\n    oracleFee: parse6decimal('0.0'),\\n    riskFee: parse6decimal('0.0'),\\n    positionFee: parse6decimal('0.0'),\\n    maxPendingGlobal: 5,\\n    maxPendingLocal: 3,\\n    settlementFee: 0,\\n    makerCloseAlways: false,\\n    takerCloseAlways: false,\\n    closed: false,\\n    settle: false,\\n  }\\n  await market.connect(owner).updateParameter(beneficiary.address, coordinator.address, marketParameter)\\n\\n  var time = TIMESTAMP;\\n\\n  setupOracle('1', time, time + 100);\\n  await market.connect(user)\\n      ['update(address,uint256,uint256,uint256,int256,bool)'](user.address, POSITION, 0, 0, COLLATERAL, false);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('1', time, time + 100);\\n  await market.connect(userB)\\n      ['update(address,uint256,uint256,uint256,int256,bool)'](userB.address, POSITION, 0, 0, COLLATERAL, false);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('1', time, time + 100);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('2', time, time + 100);\\n  await market.connect(userB)\\n      ['update(address,uint256,uint256,uint256,int256,bool)'](userB.address, 0, 0, 0, 0, false);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('2', time, time + 100);\\n  await market.connect(user)\\n      ['update(address,uint256,uint256,uint256,int256,bool)'](user.address, 0, 0, 0, 0, false);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('0.5', time, time + 100);\\n  await showInfo()\\n  await showVer(time)\\n})\\n```\\n\\nConsole log:\\n```\\nuser collateral = 10000000000\\nuserB collateral = 0\\nver1636401093: makerValue=0 longValue=0 makerPosFee=0 makerNegFee=0 takerPosFee=0 takerNegFee=0\\nuser collateral = 10000090000\\nuserB collateral = 10000000000\\nver1636401193: makerValue=0 longValue=0 makerPosFee=9000 makerNegFee=0 takerPosFee=0 takerNegFee=0\\nuser collateral = 10000090000\\nuserB collateral = 10000070000\\nver1636401293: makerValue=0 longValue=0 makerPosFee=7000 makerNegFee=0 takerPosFee=0 takerNegFee=0\\nuser collateral = 10000170000\\nuserB collateral = 10000150000\\nver1636401393: makerValue=8000 longValue=0 makerPosFee=0 makerNegFee=0 takerPosFee=0 takerNegFee=0\\nuser collateral = 10000170000\\nuserB collateral = 10000010000\\nver1636401493: makerValue=8000 longValue=0 makerPosFee=0 makerNegFee=-14000 takerPosFee=0 takerNegFee=0\\nuser collateral = 9999990000\\nuserB collateral = 10000010000\\nver1636401593: makerValue=-5500 longValue=0 makerPosFee=0 makerNegFee=-4500 takerPosFee=0 takerNegFee=0\\n```\\n\\nNotice, that final user balance is -0.1 and final userB balance is +0.1
Split the total maker exposure by individual maker's exposure rather than by their position size. To do this:\\nAdd another accumulator to track total `exposure`\\nAdd individual maker `exposure` to user's `Local` storage\\nWhen accumulating local storage in the checkpoint, account global accumulator `exposure` weighted by individual user's `exposure`.
Individual makers bear an additional undocumented price risk due to adiabatic fees, which is quite significant (can be several percentages of the notional).
```\\nit('adiabatic fee', async () => {\\n  function 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\\n  async function showInfo() {\\n    await market.settle(user.address);\\n    await market.settle(userB.address);\\n    await market.settle(userC.address);\\n    var sum : BigNumber = BigNumber.from('0');\\n    var info = await market.locals(user.address);\\n    console.log("user collateral = " + info.collateral);\\n    sum = sum.add(info.collateral);\\n    var info = await market.locals(userB.address);\\n    sum = sum.add(info.collateral);\\n    console.log("userB collateral = " + info.collateral);\\n    var info = await market.locals(userC.address);\\n    sum = sum.add(info.collateral);\\n  }\\n\\n  async function showVer(ver : number) {\\n    var v = await market.versions(ver);\\n    console.log("ver" + ver + ": makerValue=" + v.makerValue + " longValue=" + v.longValue + \\n    " makerPosFee=" + v.makerPosFee + " makerNegFee=" + v.makerNegFee +\\n    " takerPosFee=" + v.takerPosFee + " takerNegFee=" + v.takerNegFee\\n    );\\n  }\\n\\n  const riskParameter = { // rest of code(await market.riskParameter()) }\\n  const riskParameterMakerFee = { // rest of coderiskParameter.makerFee }\\n  riskParameterMakerFee.linearFee = parse6decimal('0.00')\\n  riskParameterMakerFee.proportionalFee = parse6decimal('0.00')\\n  riskParameterMakerFee.adiabaticFee = parse6decimal('0.01')\\n  riskParameterMakerFee.scale = parse6decimal('50.0')\\n  riskParameter.makerFee = riskParameterMakerFee\\n  const riskParameterTakerFee = { // rest of coderiskParameter.takerFee }\\n  riskParameterTakerFee.linearFee = parse6decimal('0.00')\\n  riskParameterTakerFee.proportionalFee = parse6decimal('0.00')\\n  riskParameterTakerFee.adiabaticFee = parse6decimal('0.01')\\n  riskParameterTakerFee.scale = parse6decimal('50.0')\\n  riskParameter.takerFee = riskParameterTakerFee\\n  await market.connect(owner).updateRiskParameter(riskParameter)\\n\\n  marketParameter = {\\n    fundingFee: parse6decimal('0.0'),\\n    interestFee: parse6decimal('0.0'),\\n    oracleFee: parse6decimal('0.0'),\\n    riskFee: parse6decimal('0.0'),\\n    positionFee: parse6decimal('0.0'),\\n    maxPendingGlobal: 5,\\n    maxPendingLocal: 3,\\n    settlementFee: 0,\\n    makerCloseAlways: false,\\n    takerCloseAlways: false,\\n    closed: false,\\n    settle: false,\\n  }\\n  await market.connect(owner).updateParameter(beneficiary.address, coordinator.address, marketParameter)\\n\\n  var time = TIMESTAMP;\\n\\n  setupOracle('1', time, time + 100);\\n  await market.connect(user)\\n      ['update(address,uint256,uint256,uint256,int256,bool)'](user.address, POSITION, 0, 0, COLLATERAL, false);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('1', time, time + 100);\\n  await market.connect(userB)\\n      ['update(address,uint256,uint256,uint256,int256,bool)'](userB.address, POSITION, 0, 0, COLLATERAL, false);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('1', time, time + 100);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('2', time, time + 100);\\n  await market.connect(userB)\\n      ['update(address,uint256,uint256,uint256,int256,bool)'](userB.address, 0, 0, 0, 0, false);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('2', time, time + 100);\\n  await market.connect(user)\\n      ['update(address,uint256,uint256,uint256,int256,bool)'](user.address, 0, 0, 0, 0, false);\\n  await showInfo()\\n  await showVer(time)\\n\\n  time += 100;\\n  setupOracle('0.5', time, time + 100);\\n  await showInfo()\\n  await showVer(time)\\n})\\n```\\n