Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Max oi cap #1669

Merged
merged 24 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion markets/perps-market/contracts/storage/AsyncOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ library AsyncOrder {
if (order.sizeDelta == 0) {
revert ZeroSizeOrder();
}

SimulateDataRuntime memory runtime;

PerpsAccount.Data storage account = PerpsAccount.load(order.accountId);
Expand Down Expand Up @@ -196,9 +197,15 @@ library AsyncOrder {
revert InsufficientMargin(runtime.currentAvailableMargin, runtime.orderFees);
}

// TODO: validate position size
oldPosition = PerpsMarket.load(order.marketId).positions[order.accountId];

PerpsMarket.validatePositionSize(
perpsMarketData,
marketConfig.maxMarketValue,
oldPosition.size,
order.sizeDelta
);

runtime.newPositionSize = oldPosition.size + order.sizeDelta;
(, , runtime.initialRequiredMargin, , ) = marketConfig.calculateRequiredMargins(
runtime.newPositionSize,
Expand Down
62 changes: 31 additions & 31 deletions markets/perps-market/contracts/storage/PerpsMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -233,43 +233,43 @@ library PerpsMarket {
return (block.timestamp - self.lastFundingTime).toInt().divDecimal(1 days);
}

// TODO: David will refactor this
function validatePositionSize(
Data storage self,
uint maxSize,
int oldSize,
int newSize
) internal view returns (bool) {
) internal view {
// Allow users to reduce an order no matter the market conditions.
if (MathUtil.sameSide(oldSize, newSize) && MathUtil.abs(newSize) <= MathUtil.abs(oldSize)) {
return false;
bool isNotReducingInterest = !(MathUtil.sameSide(oldSize, newSize) &&
MathUtil.abs(newSize) <= MathUtil.abs(oldSize));
if (isNotReducingInterest) {
int newSkew = self.skew - oldSize + newSize;

int newMarketSize = self.size.toInt() -
MathUtil.abs(oldSize).toInt() +
MathUtil.abs(newSize).toInt();

int newSideSize;
if (0 < newSize) {
// long case: marketSize + skew
// = (|longSize| + |shortSize|) + (longSize + shortSize)
// = 2 * longSize
newSideSize = newMarketSize + newSkew;
} else {
// short case: marketSize - skew
// = (|longSize| + |shortSize|) - (longSize + shortSize)
// = 2 * -shortSize
newSideSize = newMarketSize - newSkew;
}

// newSideSize still includes an extra factor of 2 here, so we will divide by 2 in the actual condition
if (maxSize < MathUtil.abs(newSideSize / 2)) {
revert PerpsMarketConfiguration.MaxOpenInterestReached(
self.id,
maxSize,
newSideSize / 2
);
}
}

// Either the user is flipping sides, or they are increasing an order on the same side they're already on;
// we check that the side of the market their order is on would not break the limit.
int newSkew = self.skew - oldSize + newSize;
int newMarketSize = self.size.toInt() -
MathUtil.abs(oldSize).toInt() +
MathUtil.abs(newSize).toInt();

int newSideSize;
if (0 < newSize) {
// long case: marketSize + skew
// = (|longSize| + |shortSize|) + (longSize + shortSize)
// = 2 * longSize
newSideSize = newMarketSize + newSkew;
} else {
// short case: marketSize - skew
// = (|longSize| + |shortSize|) - (longSize + shortSize)
// = 2 * -shortSize
newSideSize = newMarketSize - newSkew;
}

// newSideSize still includes an extra factor of 2 here, so we will divide by 2 in the actual condition
if (maxSize < MathUtil.abs(newSideSize / 2)) {
return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ library PerpsMarketConfiguration {
using DecimalMath for uint256;
using SafeCastI128 for int128;

error MaxOpenInterestReached(uint128 marketId, uint256 maxMarketValue, int newSideSize);

error InvalidSettlementStrategy(uint128 settlementStrategyId);

struct Data {
Expand Down
20 changes: 20 additions & 0 deletions markets/perps-market/test/integration/Market/CreateMarket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ describe('Create Market test', () => {
);
});
});

describe('after market is created', () => {
before('set max market value', async () => {
tx = await systems()
.PerpsMarket.connect(marketOwner)
.setMaxMarketValue(marketId, bn(99999999));
});

it('should emit MaxMarketValueSet event', async () => {
await assertEvent(
tx,
`MaxMarketValueSet(${marketId}, ${bn(99999999).toString()})`,
systems().PerpsMarket
);
});
});
});

describe('change ownership', async () => {
Expand Down Expand Up @@ -178,6 +194,10 @@ describe('Create Market test', () => {
await systems().PerpsMarket.createMarket(name, token, marketOwner.getAddress());
});

before('set max market value', async () => {
await systems().PerpsMarket.connect(marketOwner).setMaxMarketValue(marketId, bn(99999999));
});

before('create price nodes', async () => {
const results = await createOracleNode(owner(), price, systems().OracleManager);
oracleNodeId = results.oracleNodeId;
Expand Down
187 changes: 184 additions & 3 deletions markets/perps-market/test/integration/Market/Size.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import assertRevert from '@synthetixio/core-utils/utils/assertions/assert-revert';
import { PerpsMarket, bn, bootstrapMarkets } from '../bootstrap';
import { openPosition } from '../helpers';
import { depositCollateral, openPosition } from '../helpers';
import assertBn from '@synthetixio/core-utils/utils/assertions/assert-bignumber';

// TODO: test maxMarketSize here as well
describe('Market - size test', () => {
const { systems, perpsMarkets, provider, trader1, trader2, keeper } = bootstrapMarkets({
const { systems, perpsMarkets, provider, trader1, trader2, keeper, restore } = bootstrapMarkets({
synthMarkets: [],
perpsMarkets: [
{
name: 'Ether',
token: 'snxETH',
price: bn(2000),
maxMarketValue: bn(10_000),
settlementStrategy: { priceDeviationTolerance: bn(50) },
},
],
traderAccountIds: [2, 3],
Expand Down Expand Up @@ -76,4 +78,183 @@ describe('Market - size test', () => {
});
});
});

describe('max market value', () => {
describe('success', () => {
beforeEach(restore);
beforeEach('add collateral to margin', async () => {
await depositCollateral({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can call modifyCollateral directly if only depositing snxUSD, just fyi 👍🏽

accountId: () => 2,
collaterals: [
{
snxUSDAmount() {
return bn(1_000_000);
},
},
],
systems,
trader: () => trader1(),
});
await depositCollateral({
accountId: () => 3,
collaterals: [
{
snxUSDAmount() {
return bn(1_000_000);
},
},
],
systems,
trader: () => trader2(),
});
});
beforeEach('open position that uses all of available market size', async () => {
await openPosition({
systems,
provider,
trader: trader1(),
accountId: 2,
keeper: keeper(),
marketId: ethMarket.marketId(),
sizeDelta: bn(10_000),
settlementStrategyId: ethMarket.strategyId(),
price: bn(2000),
});
});
it('if user reduces size of his own trade', async () => {
await openPosition({
systems,
provider,
trader: trader1(),
accountId: 2,
keeper: keeper(),
marketId: ethMarket.marketId(),
sizeDelta: bn(-1000),
settlementStrategyId: ethMarket.strategyId(),
price: bn(2000),
});
assertBn.equal(
(await systems().PerpsMarket.getMarketSummary(ethMarket.marketId())).size,
bn(9_000)
);
});

it('if max market size: 10_000, current size: 10_000, opening short: -9_000, results in 19_000 oi', async () => {
await openPosition({
systems,
provider,
trader: trader2(),
accountId: 3,
keeper: keeper(),
marketId: ethMarket.marketId(),
sizeDelta: bn(-9_000),
settlementStrategyId: ethMarket.strategyId(),
price: bn(2000),
});
assertBn.equal(
(await systems().PerpsMarket.getMarketSummary(ethMarket.marketId())).size,
bn(19_000)
);
});

it('if max market size: 10_000, current size: 10_000, opening short: 10_000, results in 20_000 oi ', async () => {
await openPosition({
systems,
provider,
trader: trader2(),
accountId: 3,
keeper: keeper(),
marketId: ethMarket.marketId(),
sizeDelta: bn(-10_000),
settlementStrategyId: ethMarket.strategyId(),
price: bn(2000),
});
assertBn.equal(
(await systems().PerpsMarket.getMarketSummary(ethMarket.marketId())).size,
bn(20_000)
);
});
});

describe('reverts', () => {
beforeEach(restore);
beforeEach('add collateral to margin', async () => {
await depositCollateral({
accountId: () => 2,
collaterals: [
{
snxUSDAmount() {
return bn(1_000_000);
},
},
],
systems,
trader: () => trader1(),
});
await depositCollateral({
accountId: () => 3,
collaterals: [
{
snxUSDAmount() {
return bn(1_000_000);
},
},
],
systems,
trader: () => trader2(),
});
});
beforeEach('open position that uses all of available market size', async () => {
await openPosition({
systems,
provider,
trader: trader1(),
accountId: 2,
keeper: keeper(),
marketId: ethMarket.marketId(),
sizeDelta: bn(10_000),
settlementStrategyId: ethMarket.strategyId(),
price: bn(2000),
});
});

it('if max market value is reached', async () => {
await assertRevert(
openPosition({
systems,
provider,
trader: trader2(),
accountId: 3,
keeper: keeper(),
marketId: ethMarket.marketId(),
sizeDelta: bn(1),
settlementStrategyId: ethMarket.strategyId(),
price: bn(2000),
}),
`MaxOpenInterestReached(${ethMarket.marketId()}, ${bn(10_000).toString()}, ${bn(
10_001
).toString()})`
);
});

it('if exceeds max market value with short', async () => {
await assertRevert(
openPosition({
systems,
provider,
trader: trader2(),
accountId: 3,
keeper: keeper(),
marketId: ethMarket.marketId(),
sizeDelta: bn(-20_000),
settlementStrategyId: ethMarket.strategyId(),
price: bn(2000),
}),
`MaxOpenInterestReached(${ethMarket.marketId()}, ${bn(10_000).toString()}, ${bn(
20_000
).toString()})`
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ export const bootstrapPerpsMarkets = (
);
});

before('set max market value', async () => {
await contracts.PerpsMarket.connect(marketOwner).setMaxMarketValue(
marketId,
maxMarketValue ? maxMarketValue : bn(10_000_000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🖖🏽

);
});

if (orderFees) {
before('set fees', async () => {
await contracts.PerpsMarket.connect(marketOwner).setOrderFees(
Expand Down Expand Up @@ -160,15 +167,6 @@ export const bootstrapPerpsMarkets = (
});
}

if (maxMarketValue) {
before('set max market value', async () => {
await contracts.PerpsMarket.connect(marketOwner).setMaxMarketValue(
marketId,
maxMarketValue
);
});
}

let strategyId: ethers.BigNumber;
// create default settlement strategy
before('create default settlement strategy', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const openPosition = async (data: OpenPositionData) => {
acceptablePrice: sizeDelta.gt(0) ? price.mul(2) : price.div(2),
trackingCode: trackingCode ?? ethers.constants.HashZero,
});

const commitmentTime = await getTxTime(provider(), commitTx);
const settlementTime = commitmentTime + delay + 1;
await fastForwardTo(settlementTime, provider());
Expand Down