Skip to content

Commit

Permalink
add efficiency limit
Browse files Browse the repository at this point in the history
  • Loading branch information
kbrizzle committed Jul 11, 2023
1 parent 9822bcb commit 4cc57cf
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export async function deployProductOnMainnetFork({
takerImpactFee,
positionFee,
makerLimit,
efficiencyLimit,
utilizationCurve,
minMaintenance,
liquidationFee,
Expand All @@ -50,6 +51,7 @@ export async function deployProductOnMainnetFork({
makerFee: makerFee ?? parse6decimal('0.0'),
makerImpactFee: makerImpactFee ?? parse6decimal('0.0'),
makerLimit: makerLimit ?? parse6decimal('100'),
efficiencyLimit: efficiencyLimit ?? parse6decimal('0.2'),
liquidationFee: liquidationFee ?? parse6decimal('0.50'),
minLiquidationFee: minLiquidationFee ?? parse6decimal('0'),
maxLiquidationFee: maxLiquidationFee ?? parse6decimal('1000'),
Expand Down
7 changes: 7 additions & 0 deletions packages/perennial/contracts/Market.sol
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,13 @@ contract Market is IMarket, Instance {
!newOrder.isEmpty()
) { if (LOG_REVERTS) console.log("MarketProtectedError"); revert MarketProtectedError(); }

if (
!protected &&
!context.marketParameter.closed &&
newOrder.efficiency.lt(Fixed6Lib.ZERO) &&
context.pendingPosition.efficiency().lt(context.riskParameter.efficiencyLimit)
) { if (LOG_REVERTS) console.log("MarketEfficiencyUnderLimitError"); revert MarketEfficiencyUnderLimitError(); }

if (
!protected &&
!context.marketParameter.closed &&
Expand Down
1 change: 1 addition & 0 deletions packages/perennial/contracts/interfaces/IMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ interface IMarket is IInstance {
error MarketNotBeneficiaryError();
error MarketInvalidProtectionError();
error MarketStalePriceError();
error MarketEfficiencyUnderLimitError();

error GlobalStorageInvalidError();
error LocalStorageInvalidError();
Expand Down
1 change: 1 addition & 0 deletions packages/perennial/contracts/types/Order.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ struct Order {
Fixed6 short;
UFixed6 skew;
Fixed6 impact;
Fixed6 efficiency;
UFixed6 fee;
UFixed6 keeper;
}
Expand Down
11 changes: 8 additions & 3 deletions packages/perennial/contracts/types/Position.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ library PositionLib {

/// @dev update the current global position
function update(Position memory self, uint256 currentId, uint256 currentTimestamp, Order memory order) internal pure {
Fixed6 latestSkew = skew(self);
(Fixed6 latestSkew, UFixed6 latestEfficiency) = (skew(self), efficiency(self));

if (self.id == currentId) self.fee = UFixed6Lib.ZERO;
(self.id, self.timestamp, self.maker, self.long, self.short) = (
Expand All @@ -99,9 +99,10 @@ library PositionLib {
UFixed6Lib.from(Fixed6Lib.from(self.short).add(order.short))
);

(order.skew, order.impact) = (
(order.skew, order.impact, order.efficiency) = (
skew(self).sub(latestSkew).abs(),
Fixed6Lib.from(skew(self).abs()).sub(Fixed6Lib.from(latestSkew.abs()))
Fixed6Lib.from(skew(self).abs()).sub(Fixed6Lib.from(latestSkew.abs())),
Fixed6Lib.from(efficiency(self)).sub(Fixed6Lib.from(latestEfficiency))
);
}

Expand Down Expand Up @@ -165,6 +166,10 @@ library PositionLib {
return major(self).min(minor(self).add(self.maker));
}

function efficiency(Position memory self) internal pure returns (UFixed6) {
return self.maker.unsafeDiv(major(self)).min(UFixed6Lib.ONE);
}

function socialized(Position memory self) internal pure returns (bool) {
return self.maker.add(self.short).lt(self.long) || self.maker.add(self.long).lt(self.short);
}
Expand Down
7 changes: 6 additions & 1 deletion packages/perennial/contracts/types/RiskParameter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct RiskParameter {
UFixed6 makerFee;
UFixed6 makerImpactFee;
UFixed6 makerLimit;
UFixed6 efficiencyLimit;
UFixed6 liquidationFee;
UFixed6 minLiquidationFee;
UFixed6 maxLiquidationFee;
Expand All @@ -26,7 +27,7 @@ struct RiskParameter {
bool makerReceiveOnly;
// TODO: closeToSocializeMaker
// TODO: closeToSocializeTaker
// TODO: takerLimit
// TODO: efficiencyLimit
}

struct StoredRiskParameter {
Expand Down Expand Up @@ -55,6 +56,7 @@ struct StoredRiskParameter {
uint24 liquidationFee; // <= 1677%
uint48 minLiquidationFee; // <= 281mn
uint48 maxLiquidationFee; // <= 281mn
uint24 efficiencyLimit; // <= 1677%
}
struct RiskParameterStorage { StoredRiskParameter value; }
using RiskParameterStorageLib for RiskParameterStorage global;
Expand All @@ -72,6 +74,7 @@ library RiskParameterStorageLib {
UFixed6.wrap(uint256(value.makerFee)),
UFixed6.wrap(uint256(value.makerImpactFee)),
UFixed6.wrap(uint256(value.makerLimit)),
UFixed6.wrap(uint256(value.efficiencyLimit)),
UFixed6.wrap(uint256(value.liquidationFee)),
UFixed6.wrap(uint256(value.minLiquidationFee)),
UFixed6.wrap(uint256(value.maxLiquidationFee)),
Expand Down Expand Up @@ -99,6 +102,7 @@ library RiskParameterStorageLib {
if (newValue.makerFee.gt(UFixed6.wrap(type(uint24).max))) revert RiskParameterStorageInvalidError();
if (newValue.makerImpactFee.gt(UFixed6.wrap(type(uint24).max))) revert RiskParameterStorageInvalidError();
if (newValue.makerLimit.gt(UFixed6.wrap(type(uint48).max))) revert RiskParameterStorageInvalidError();
if (newValue.efficiencyLimit.gt(UFixed6.wrap(type(uint24).max))) revert RiskParameterStorageInvalidError();
if (newValue.liquidationFee.gt(UFixed6.wrap(type(uint24).max))) revert RiskParameterStorageInvalidError();
if (newValue.minLiquidationFee.gt(UFixed6.wrap(type(uint48).max))) revert RiskParameterStorageInvalidError();
if (newValue.maxLiquidationFee.gt(UFixed6.wrap(type(uint48).max))) revert RiskParameterStorageInvalidError();
Expand All @@ -119,6 +123,7 @@ library RiskParameterStorageLib {
makerFee: uint24(UFixed6.unwrap(newValue.makerFee)),
makerImpactFee: uint24(UFixed6.unwrap(newValue.makerImpactFee)),
makerLimit: uint48(UFixed6.unwrap(newValue.makerLimit)),
efficiencyLimit: uint24(UFixed6.unwrap(newValue.efficiencyLimit)),
liquidationFee: uint24(UFixed6.unwrap(newValue.liquidationFee)),
minLiquidationFee: uint48(UFixed6.unwrap(newValue.minLiquidationFee)),
maxLiquidationFee: uint48(UFixed6.unwrap(newValue.maxLiquidationFee)),
Expand Down
10 changes: 5 additions & 5 deletions packages/perennial/test/integration/core/happyPath.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ describe('Happy Path', () => {
takerImpactFee: 0,
makerFee: 0,
makerImpactFee: 0,
makerLiquidity: parse6decimal('0.2'),
makerLimit: parse6decimal('1'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down Expand Up @@ -778,7 +778,7 @@ describe('Happy Path', () => {

await expect(
market.connect(userB).update(userB.address, 0, POSITION_B, 0, COLLATERAL, false),
).to.be.revertedWithCustomError(market, 'MarketInsufficientLiquidityError')
).to.be.revertedWithCustomError(market, 'MarketEfficiencyUnderLimitError')
await market.connect(user).update(user.address, POSITION, 0, 0, COLLATERAL, false)
await market.connect(userB).update(userB.address, 0, POSITION_B, 0, COLLATERAL, false)

Expand Down Expand Up @@ -856,7 +856,7 @@ describe('Happy Path', () => {

await expect(
market.connect(userB).update(userB.address, 0, POSITION_B, 0, COLLATERAL, false),
).to.be.revertedWithCustomError(market, 'MarketInsufficientLiquidityError')
).to.be.revertedWithCustomError(market, 'MarketEfficiencyUnderLimitError')
await market.connect(user).update(user.address, POSITION, 0, 0, COLLATERAL, false)
await market.connect(userB).update(userB.address, 0, POSITION_B, 0, COLLATERAL, false)
await market.connect(userB).update(userB.address, POSITION_B.div(2), 0, 0, 0, false)
Expand Down Expand Up @@ -1015,8 +1015,8 @@ describe('Happy Path', () => {
takerImpactFee: positionFeesOn ? parse6decimal('0.0004') : 0,
makerFee: positionFeesOn ? parse6decimal('0.0005') : 0,
makerImpactFee: positionFeesOn ? parse6decimal('0.0002') : 0,
makerLiquidity: parse6decimal('0.2'),
makerLimit: parse6decimal('1'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down Expand Up @@ -1153,8 +1153,8 @@ describe('Happy Path', () => {
takerImpactFee: positionFeesOn ? parse6decimal('0.0004') : 0,
makerFee: positionFeesOn ? parse6decimal('0.0005') : 0,
makerImpactFee: positionFeesOn ? parse6decimal('0.0002') : 0,
makerLiquidity: parse6decimal('0.2'),
makerLimit: parse6decimal('1'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ export async function createMarket(
takerImpactFee: 0,
makerFee: 0,
makerImpactFee: 0,
makerLiquidity: parse6decimal('0.2'),
makerLimit: parse6decimal('1000'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down
27 changes: 25 additions & 2 deletions packages/perennial/test/unit/market/Market.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ describe('Market', () => {
makerFee: 0,
makerImpactFee: 0,
makerLimit: parse6decimal('1000'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down Expand Up @@ -301,6 +302,7 @@ describe('Market', () => {
expect(riskParameterResult.makerFee).to.equal(riskParameter.makerFee)
expect(riskParameterResult.makerImpactFee).to.equal(riskParameter.makerImpactFee)
expect(riskParameterResult.makerLimit).to.equal(riskParameter.makerLimit)
expect(riskParameterResult.efficiencyLimit).to.equal(riskParameter.efficiencyLimit)
expect(riskParameterResult.liquidationFee).to.equal(riskParameter.liquidationFee)
expect(riskParameterResult.minLiquidationFee).to.equal(riskParameter.minLiquidationFee)
expect(riskParameterResult.maxLiquidationFee).to.equal(riskParameter.maxLiquidationFee)
Expand Down Expand Up @@ -416,8 +418,8 @@ describe('Market', () => {
takerImpactFee: parse6decimal('0.03'),
makerFee: parse6decimal('0.05'),
makerImpactFee: parse6decimal('0.01'),
makerLiquidity: parse6decimal('0.1'),
makerLimit: parse6decimal('2000'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.75'),
minLiquidationFee: parse6decimal('10'),
maxLiquidationFee: parse6decimal('200'),
Expand Down Expand Up @@ -449,6 +451,7 @@ describe('Market', () => {
expect(riskParameter.makerFee).to.equal(newRiskParameter.makerFee)
expect(riskParameter.makerImpactFee).to.equal(newRiskParameter.makerImpactFee)
expect(riskParameter.makerLimit).to.equal(newRiskParameter.makerLimit)
expect(riskParameter.efficiencyLimit).to.equal(newRiskParameter.efficiencyLimit)
expect(riskParameter.liquidationFee).to.equal(newRiskParameter.liquidationFee)
expect(riskParameter.minLiquidationFee).to.equal(newRiskParameter.minLiquidationFee)
expect(riskParameter.maxLiquidationFee).to.equal(newRiskParameter.maxLiquidationFee)
Expand Down Expand Up @@ -10190,6 +10193,23 @@ describe('Market', () => {
).to.be.revertedWithCustomError(market, 'MarketMakerOverLimitError')
})

it('reverts if under efficiency limit', async () => {
const riskParameter = { ...(await market.riskParameter()) }
riskParameter.efficiencyLimit = parse6decimal('0.6')
await market.updateRiskParameter(riskParameter)

dsu.transferFrom.whenCalledWith(user.address, market.address, COLLATERAL.mul(1e12)).returns(true)
await market.connect(user).update(user.address, POSITION.div(2), 0, 0, COLLATERAL, false)
dsu.transferFrom.whenCalledWith(userB.address, market.address, COLLATERAL.mul(1e12)).returns(true)
await market.connect(userB).update(userB.address, 0, POSITION.div(2), 0, COLLATERAL, false)
dsu.transferFrom.whenCalledWith(userC.address, market.address, COLLATERAL.mul(1e12)).returns(true)
await market.connect(userC).update(userC.address, 0, 0, POSITION.div(2), COLLATERAL, false)

await expect(
market.connect(userB).update(userB.address, 0, POSITION, 0, 0, false),
).to.be.revertedWithCustomError(market, 'MarketEfficiencyUnderLimitError')
})

it('reverts if under minimum maintenance', async () => {
dsu.transferFrom.whenCalledWith(user.address, market.address, utils.parseEther('1')).returns(true)
await expect(
Expand All @@ -10207,8 +10227,11 @@ describe('Market', () => {
})

it('reverts if taker > maker', async () => {
dsu.transferFrom.whenCalledWith(user.address, market.address, COLLATERAL.mul(1e12)).returns(true)
await market.connect(user).update(user.address, POSITION, 0, 0, COLLATERAL, false)

await expect(
market.connect(user).update(user.address, 0, POSITION.mul(4), 0, COLLATERAL, false),
market.connect(userB).update(userB.address, 0, POSITION.add(1), 0, COLLATERAL, false),
).to.be.revertedWithCustomError(market, `MarketInsufficientLiquidityError`)
})

Expand Down
10 changes: 5 additions & 5 deletions packages/perennial/test/unit/marketfactory/MarketFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ describe('MarketFactory', () => {
makerFee: 0,
makerImpactFee: 0,
positionFee: 0,
makerLiquidity: parse6decimal('0.2'),
makerLimit: parse6decimal('1000'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down Expand Up @@ -136,8 +136,8 @@ describe('MarketFactory', () => {
makerFee: 0,
makerImpactFee: 0,
positionFee: 0,
makerLiquidity: parse6decimal('0.2'),
makerLimit: parse6decimal('1000'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down Expand Up @@ -185,8 +185,8 @@ describe('MarketFactory', () => {
makerFee: 0,
makerImpactFee: 0,
positionFee: 0,
makerLiquidity: parse6decimal('0.2'),
makerLimit: parse6decimal('1000'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down Expand Up @@ -232,8 +232,8 @@ describe('MarketFactory', () => {
makerFee: 0,
makerImpactFee: 0,
positionFee: 0,
makerLiquidity: parse6decimal('0.2'),
makerLimit: parse6decimal('1000'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down Expand Up @@ -279,8 +279,8 @@ describe('MarketFactory', () => {
makerFee: 0,
makerImpactFee: 0,
positionFee: 0,
makerLiquidity: parse6decimal('0.2'),
makerLimit: parse6decimal('1000'),
efficiencyLimit: parse6decimal('0.2'),
liquidationFee: parse6decimal('0.50'),
minLiquidationFee: parse6decimal('0'),
maxLiquidationFee: parse6decimal('1000'),
Expand Down

0 comments on commit 4cc57cf

Please sign in to comment.