Skip to content

Commit

Permalink
add param invariants
Browse files Browse the repository at this point in the history
  • Loading branch information
kbrizzle committed Jul 13, 2023
1 parent 241a527 commit 1d71e1c
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 63 deletions.
5 changes: 2 additions & 3 deletions packages/perennial-vault/contracts/Vault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,6 @@ contract Vault is IVault, Instance {
function _loadContext(address account) private view returns (Context memory context) {
context.parameter = _parameter.read();

ProtocolParameter memory protocolParameter = IVaultFactory(address(factory())).marketFactory().parameter();
context.settlementFee = protocolParameter.settlementFee;

context.currentIds.initialize(totalMarkets);
context.latestIds.initialize(totalMarkets);
context.registrations = new Registration[](totalMarkets);
Expand All @@ -391,7 +388,9 @@ contract Vault is IVault, Instance {
for (uint256 marketId; marketId < totalMarkets; marketId++) {
// parameter
Registration memory registration = _registrations[marketId].read();
MarketParameter memory marketParameter = registration.market.parameter();
context.registrations[marketId] = registration;
context.settlementFee = context.settlementFee.add(marketParameter.settlementFee);

// global
Global memory global = registration.market.global();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export async function deployProductOnMainnetFork({
positionFee: positionFee ?? parse6decimal('0.0'),
riskFee: 0,
oracleFee: 0,
settlementFee: 0,
makerRewardRate: 0,
longRewardRate: 0,
shortRewardRate: 0,
Expand All @@ -90,6 +91,10 @@ export async function deployProductOnMainnetFork({
payoff: payoff ?? constants.AddressZero,
}

const protocolParameter = { ...(await factory.parameter()) }
protocolParameter.maxFeeAbsolute = parse6decimal('25000')
await factory.connect(owner).updateParameter(protocolParameter)

const productAddress = await factory.connect(owner).callStatic.create(marketDefinition, riskParameter)
await factory.connect(owner).create(marketDefinition, riskParameter)

Expand Down
56 changes: 51 additions & 5 deletions packages/perennial/contracts/Market.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ contract Market is IMarket, Instance {
token = definition_.token;
oracle = definition_.oracle;
payoff = definition_.payoff;
_updateRiskParameter(riskParameter_);
_updateRiskParameter(riskParameter_); // TODO: don't set or use version with invariant
}

function update(
Expand Down Expand Up @@ -101,21 +101,66 @@ contract Market is IMarket, Instance {
}

function updateParameter(MarketParameter memory newParameter) external onlyOwner {
ProtocolParameter memory protocolParameter = IMarketFactory(address(factory())).parameter();

if (newParameter.fundingFee.gt(protocolParameter.maxCut)) revert MarketInvalidMarketParameterError(1);
if (newParameter.interestFee.gt(protocolParameter.maxCut)) revert MarketInvalidMarketParameterError(2);
if (newParameter.positionFee.gt(protocolParameter.maxCut)) revert MarketInvalidMarketParameterError(3);
if (newParameter.riskFee.gt(UFixed6Lib.ONE)) revert MarketInvalidMarketParameterError(4);
if (newParameter.oracleFee.gt(UFixed6Lib.ONE)) revert MarketInvalidMarketParameterError(5);
if (newParameter.settlementFee.gt(protocolParameter.maxFeeAbsolute))
revert MarketInvalidMarketParameterError(6);
if (newParameter.oracleFee.add(newParameter.riskFee).gt(UFixed6Lib.ONE))
revert MarketInvalidParameterError();
// TODO: if reward not set don't allow reward rate to be non-zero
revert MarketInvalidMarketParameterError(7);
if (reward.isZero() && (
!newParameter.makerRewardRate.isZero() ||
!newParameter.longRewardRate.isZero() ||
!newParameter.shortRewardRate.isZero()
)) revert MarketInvalidMarketParameterError(8);

_parameter.store(newParameter);
emit ParameterUpdated(newParameter);
}

function updateRiskParameter(RiskParameter memory newRiskParameter) external onlyCoordinator {
ProtocolParameter memory protocolParameter = IMarketFactory(address(factory())).parameter();

if (newRiskParameter.maintenance.lt(protocolParameter.minMaintenance))
revert MarketInvalidRiskParameterError(1);
if (newRiskParameter.takerFee.gt(protocolParameter.maxFee)) revert MarketInvalidRiskParameterError(2);
if (newRiskParameter.takerSkewFee.gt(protocolParameter.maxFee)) revert MarketInvalidRiskParameterError(3);
if (newRiskParameter.takerImpactFee.gt(protocolParameter.maxFee)) revert MarketInvalidRiskParameterError(4);
if (newRiskParameter.makerFee.gt(protocolParameter.maxFee)) revert MarketInvalidRiskParameterError(5);
if (newRiskParameter.makerImpactFee.gt(protocolParameter.maxFee)) revert MarketInvalidRiskParameterError(6);
if (newRiskParameter.efficiencyLimit.lt(protocolParameter.minEfficiency))
revert MarketInvalidRiskParameterError(7);
if (newRiskParameter.liquidationFee.gt(protocolParameter.maxCut)) revert MarketInvalidRiskParameterError(8);
if (newRiskParameter.minLiquidationFee.gt(protocolParameter.maxFeeAbsolute))
revert MarketInvalidRiskParameterError(9);
if (newRiskParameter.maxLiquidationFee.gt(protocolParameter.maxFeeAbsolute))
revert MarketInvalidRiskParameterError(10);
if (newRiskParameter.utilizationCurve.minRate.gt(protocolParameter.maxRate))
revert MarketInvalidRiskParameterError(11);
if (newRiskParameter.utilizationCurve.maxRate.gt(protocolParameter.maxRate))
revert MarketInvalidRiskParameterError(12);
if (newRiskParameter.utilizationCurve.targetRate.gt(protocolParameter.maxRate))
revert MarketInvalidRiskParameterError(13);
if (newRiskParameter.utilizationCurve.targetUtilization.gt(UFixed6Lib.ONE))
revert MarketInvalidRiskParameterError(14);
if (newRiskParameter.pController.max.gt(protocolParameter.maxRate))
revert MarketInvalidRiskParameterError(15);
if (
newRiskParameter.minMaintenance.gt(protocolParameter.maxFeeAbsolute) ||
newRiskParameter.minMaintenance.lt(newRiskParameter.minLiquidationFee)
) revert MarketInvalidRiskParameterError(16);

_updateRiskParameter(newRiskParameter);
}

function updateReward(Token18 newReward) public onlyOwner {
if (!reward.eq(Token18Lib.ZERO)) revert MarketRewardAlreadySetError();
if (newReward.eq(token)) revert MarketInvalidRewardError();

reward = newReward;
emit RewardUpdated(newReward);
}
Expand Down Expand Up @@ -215,7 +260,7 @@ contract Market is IMarket, Instance {
context.pendingPosition.update(context.global.currentId, context.currentTimestamp, newOrder);

// update fee
newOrder.registerFee(context.latestVersion, context.protocolParameter, context.riskParameter);
newOrder.registerFee(context.latestVersion, context.marketParameter, context.riskParameter);
context.accountPendingPosition.registerFee(newOrder);
context.pendingPosition.registerFee(newOrder);

Expand Down Expand Up @@ -302,7 +347,8 @@ contract Market is IMarket, Instance {

while (
context.local.currentId != context.accountPosition.id &&
(nextPosition = _pendingPositions[account][context.accountPosition.id + 1].read()).ready(context.latestVersion)
(nextPosition = _pendingPositions[account][context.accountPosition.id + 1].read())
.ready(context.latestVersion)
) {
Fixed6 previousDelta = _pendingPositions[account][context.accountPosition.id].read().delta;
_processPositionAccount(context, nextPosition);
Expand Down
3 changes: 2 additions & 1 deletion packages/perennial/contracts/interfaces/IMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ interface IMarket is IInstance {
error MarketExceedsPendingIdLimitError();
error MarketRewardAlreadySetError();
error MarketInvalidRewardError();
error MarketInvalidParameterError();
error MarketNotCoordinatorError();
error MarketNotBeneficiaryError();
error MarketInvalidProtectionError();
error MarketStalePriceError();
error MarketEfficiencyUnderLimitError();
error MarketInvalidMarketParameterError(uint256 code);
error MarketInvalidRiskParameterError(uint256 code);

error GlobalStorageInvalidError();
error LocalStorageInvalidError();
Expand Down
29 changes: 18 additions & 11 deletions packages/perennial/contracts/types/MarketParameter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct MarketParameter {
UFixed6 positionFee;
UFixed6 oracleFee; // TODO: move to oracle?
UFixed6 riskFee;
UFixed6 settlementFee;
UFixed6 makerRewardRate;
UFixed6 longRewardRate;
UFixed6 shortRewardRate;
Expand All @@ -28,13 +29,11 @@ struct StoredMarketParameter {
uint24 positionFee; // <= 1677%
uint24 oracleFee; // <= 1677%
uint24 riskFee; // <= 1677%
uint32 settlementFee; // <= 4294 // TODO: ??
uint32 makerRewardRate; // <= 2147.48 / s
uint32 longRewardRate; // <= 2147.48 / s
uint32 shortRewardRate; // <= 2147.48 / s
bool takerCloseAlways;
bool makerCloseAlways;
bool closed;
bytes2 __unallocated__;
uint8 flags;
}
struct MarketParameterStorage { StoredMarketParameter value; }
using MarketParameterStorageLib for MarketParameterStorage global;
Expand All @@ -44,18 +43,23 @@ library MarketParameterStorageLib {

function read(MarketParameterStorage storage self) internal view returns (MarketParameter memory) {
StoredMarketParameter memory value = self.value;

(bool takerCloseAlways, bool makerCloseAlways, bool closed) =
(value.flags & 0x01 == 0x01, value.flags & 0x02 == 0x02, value.flags & 0x04 == 0x04);

return MarketParameter(
UFixed6.wrap(uint256(value.fundingFee)),
UFixed6.wrap(uint256(value.interestFee)),
UFixed6.wrap(uint256(value.positionFee)),
UFixed6.wrap(uint256(value.oracleFee)),
UFixed6.wrap(uint256(value.riskFee)),
UFixed6.wrap(uint256(value.settlementFee)),
UFixed6.wrap(uint256(value.makerRewardRate)),
UFixed6.wrap(uint256(value.longRewardRate)),
UFixed6.wrap(uint256(value.shortRewardRate)),
value.takerCloseAlways,
value.makerCloseAlways,
value.closed
takerCloseAlways,
makerCloseAlways,
closed
);
}

Expand All @@ -65,23 +69,26 @@ library MarketParameterStorageLib {
if (newValue.positionFee.gt(UFixed6.wrap(type(uint24).max))) revert MarketParameterStorageInvalidError();
if (newValue.oracleFee.gt(UFixed6.wrap(type(uint24).max))) revert MarketParameterStorageInvalidError();
if (newValue.riskFee.gt(UFixed6.wrap(type(uint24).max))) revert MarketParameterStorageInvalidError();
if (newValue.settlementFee.gt(UFixed6.wrap(type(uint32).max))) revert MarketParameterStorageInvalidError();
if (newValue.makerRewardRate.gt(UFixed6.wrap(type(uint32).max))) revert MarketParameterStorageInvalidError();
if (newValue.longRewardRate.gt(UFixed6.wrap(type(uint32).max))) revert MarketParameterStorageInvalidError();
if (newValue.shortRewardRate.gt(UFixed6.wrap(type(uint32).max))) revert MarketParameterStorageInvalidError();

uint8 flags = (newValue.takerCloseAlways ? 0x01 : 0x00) |
(newValue.makerCloseAlways ? 0x02 : 0x00) |
(newValue.closed ? 0x04 : 0x00);

self.value = StoredMarketParameter(
uint24(UFixed6.unwrap(newValue.fundingFee)),
uint24(UFixed6.unwrap(newValue.interestFee)),
uint24(UFixed6.unwrap(newValue.positionFee)),
uint24(UFixed6.unwrap(newValue.oracleFee)),
uint24(UFixed6.unwrap(newValue.riskFee)),
uint32(UFixed6.unwrap(newValue.settlementFee)),
uint32(UFixed6.unwrap(newValue.makerRewardRate)),
uint32(UFixed6.unwrap(newValue.longRewardRate)),
uint32(UFixed6.unwrap(newValue.shortRewardRate)),
newValue.takerCloseAlways,
newValue.makerCloseAlways,
newValue.closed,
bytes2(0)
flags
);
}
}
6 changes: 3 additions & 3 deletions packages/perennial/contracts/types/Order.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
pragma solidity ^0.8.13;

import "@equilibria/perennial-v2-oracle/contracts/types/OracleVersion.sol";
import "./ProtocolParameter.sol";
import "./RiskParameter.sol";
import "./MarketParameter.sol";

/// @dev Order type
struct Order {
Expand All @@ -26,7 +26,7 @@ library OrderLib {
function registerFee(
Order memory self,
OracleVersion memory latestVersion,
ProtocolParameter memory protocolParameter,
MarketParameter memory marketParameter,
RiskParameter memory riskParameter
) internal pure {
Fixed6 makerFee = Fixed6Lib.from(riskParameter.makerFee)
Expand All @@ -40,7 +40,7 @@ library OrderLib {
self.fee = self.maker.abs().mul(latestVersion.price.abs()).mul(UFixed6Lib.from(makerFee))
.add(self.long.abs().add(self.short.abs()).mul(latestVersion.price.abs()).mul(UFixed6Lib.from(takerFee)));

self.keeper = isEmpty(self) ? UFixed6Lib.ZERO : protocolParameter.settlementFee;
self.keeper = isEmpty(self) ? UFixed6Lib.ZERO : marketParameter.settlementFee;
}

function increasesPosition(Order memory self) internal pure returns (bool) {
Expand Down
49 changes: 37 additions & 12 deletions packages/perennial/contracts/types/ProtocolParameter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@ import "@equilibria/root/number/types/UFixed6.sol";

/// @dev ProtocolParameter type
struct ProtocolParameter {
UFixed6 protocolFee;
UFixed6 settlementFee; // TODO: move to oracle
uint256 maxPendingIds;
UFixed6 protocolFee;
UFixed6 maxFee;
UFixed6 maxFeeAbsolute;
UFixed6 maxCut;
UFixed6 maxRate;
UFixed6 minMaintenance;
UFixed6 minEfficiency;
}
struct StoredProtocolParameter {
uint24 _protocolFee; // <= 1677%
uint24 _settlementFee; // <= 1677%
uint8 _maxPendingIds; // <= 255
uint24 _protocolFee; // <= 1677%
uint24 _maxFee; // <= 1677%
uint48 _maxFeeAbsolute; // <= 281m
uint24 _maxCut; // <= 1677%
uint32 _maxRate; // <= 429496%
uint24 _minMaintenance; // <= 1677%
uint24 _minEfficiency; // <= 1677%

bytes16 __unallocated__;
bytes6 __unallocated__;
}
struct ProtocolParameterStorage { StoredProtocolParameter value; }
using ProtocolParameterStorageLib for ProtocolParameterStorage global;
Expand All @@ -25,22 +35,37 @@ library ProtocolParameterStorageLib {
function read(ProtocolParameterStorage storage self) internal view returns (ProtocolParameter memory) {
StoredProtocolParameter memory value = self.value;
return ProtocolParameter(
uint256(value._maxPendingIds),
UFixed6.wrap(uint256(value._protocolFee)),
UFixed6.wrap(uint256(value._settlementFee)),
uint256(value._maxPendingIds)
UFixed6.wrap(uint256(value._maxFee)),
UFixed6.wrap(uint256(value._maxFeeAbsolute)),
UFixed6.wrap(uint256(value._maxCut)),
UFixed6.wrap(uint256(value._maxRate)),
UFixed6.wrap(uint256(value._minMaintenance)),
UFixed6.wrap(uint256(value._minEfficiency))
);
}

function store(ProtocolParameterStorage storage self, ProtocolParameter memory newValue) internal {
if (newValue.protocolFee.gt(UFixed6.wrap(type(uint24).max))) revert ProtocolParameterStorageInvalidError();
if (newValue.settlementFee.gt(UFixed6.wrap(type(uint24).max))) revert ProtocolParameterStorageInvalidError();
if (newValue.maxPendingIds > uint256(type(uint8).max)) revert ProtocolParameterStorageInvalidError();
if (newValue.protocolFee.gt(UFixed6.wrap(type(uint24).max))) revert ProtocolParameterStorageInvalidError();
if (newValue.maxFee.gt(UFixed6.wrap(type(uint24).max))) revert ProtocolParameterStorageInvalidError();
if (newValue.maxFeeAbsolute.gt(UFixed6.wrap(type(uint48).max))) revert ProtocolParameterStorageInvalidError();
if (newValue.maxCut.gt(UFixed6.wrap(type(uint24).max))) revert ProtocolParameterStorageInvalidError();
if (newValue.maxRate.gt(UFixed6.wrap(type(uint32).max))) revert ProtocolParameterStorageInvalidError();
if (newValue.minMaintenance.gt(UFixed6.wrap(type(uint24).max))) revert ProtocolParameterStorageInvalidError();
if (newValue.minEfficiency.gt(UFixed6.wrap(type(uint24).max))) revert ProtocolParameterStorageInvalidError();

self.value = StoredProtocolParameter(
uint24(UFixed6.unwrap(newValue.protocolFee)),
uint24(UFixed6.unwrap(newValue.settlementFee)),
uint8(newValue.maxPendingIds),
bytes10(0)
uint24(UFixed6.unwrap(newValue.protocolFee)),
uint24(UFixed6.unwrap(newValue.maxFee)),
uint48(UFixed6.unwrap(newValue.maxFeeAbsolute)),
uint24(UFixed6.unwrap(newValue.maxCut)),
uint32(UFixed6.unwrap(newValue.maxRate)),
uint24(UFixed6.unwrap(newValue.minMaintenance)),
uint24(UFixed6.unwrap(newValue.minEfficiency)),
bytes6(0)
);
}
}
3 changes: 3 additions & 0 deletions packages/perennial/test/integration/core/happyPath.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe('Happy Path', () => {
oracleFee: 0,
riskFee: 0,
positionFee: 0,
settlementFee: 0,
makerRewardRate: 0,
longRewardRate: 0,
shortRewardRate: 0,
Expand Down Expand Up @@ -1041,6 +1042,7 @@ describe('Happy Path', () => {
interestFee: parse6decimal('0.1'),
oracleFee: 0,
riskFee: 0,
settlementFee: 0,
positionFee: positionFeesOn ? parse6decimal('0.1') : 0,
makerRewardRate: incentizesOn ? parse6decimal('0.01') : 0,
longRewardRate: incentizesOn ? parse6decimal('0.001') : 0,
Expand Down Expand Up @@ -1182,6 +1184,7 @@ describe('Happy Path', () => {
interestFee: parse6decimal('0.1'),
oracleFee: 0,
riskFee: 0,
settlementFee: 0,
positionFee: positionFeesOn ? parse6decimal('0.1') : 0,
makerRewardRate: incentizesOn ? parse6decimal('0.01') : 0,
longRewardRate: incentizesOn ? parse6decimal('0.001') : 0,
Expand Down
10 changes: 8 additions & 2 deletions packages/perennial/test/integration/helpers/setupHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,13 @@ export async function deployProtocol(): Promise<InstanceVars> {
await marketFactory.updatePauser(pauser.address)
await marketFactory.updateParameter({
protocolFee: parse6decimal('0.50'),
settlementFee: parse6decimal('0.00'),
maxPendingIds: 8,
maxFee: parse6decimal('0.01'),
maxFeeAbsolute: parse6decimal('1000'),
maxCut: parse6decimal('0.50'),
maxRate: parse6decimal('10.00'),
minMaintenance: parse6decimal('0.01'),
minEfficiency: parse6decimal('0.1'),
})
await payoffFactory.connect(owner).register(payoff.address)
await oracleFactory.connect(owner).register(chainlink.oracleFactory.address)
Expand Down Expand Up @@ -229,6 +234,7 @@ export async function createMarket(
makerRewardRate: 0,
longRewardRate: 0,
shortRewardRate: 0,
settlementFee: 0,
makerCloseAlways: false,
takerCloseAlways: false,
closed: false,
Expand All @@ -237,9 +243,9 @@ export async function createMarket(
await marketFactory.create(definition, riskParameter)

const market = Market__factory.connect(marketAddress, owner)
await market.updateParameter(marketParameter)
await market.updateBeneficiary(beneficiaryB.address)
await market.updateReward(rewardToken.address)
await market.updateParameter(marketParameter)

return market
}
Expand Down
Loading

0 comments on commit 1d71e1c

Please sign in to comment.