Skip to content

Commit

Permalink
add vault minimum
Browse files Browse the repository at this point in the history
  • Loading branch information
kbrizzle committed Jul 21, 2023
1 parent c3190c7 commit 4852cfe
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 8 deletions.
14 changes: 11 additions & 3 deletions packages/perennial-vault/contracts/Vault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,15 @@ contract Vault is IVault, Instance {
revert VaultNotOperatorError();
if (!depositAssets.add(redeemShares).add(claimAssets).eq(depositAssets.max(redeemShares).max(claimAssets)))
revert VaultNotSingleSidedError();
if (depositAssets.gt(_maxDeposit(context))) revert VaultDepositLimitExceededError();
if (redeemShares.gt(_maxRedeem(context))) revert VaultRedemptionLimitExceededError();
if (depositAssets.gt(_maxDeposit(context)))
revert VaultDepositLimitExceededError();
if (redeemShares.gt(_maxRedeem(context)))
revert VaultRedemptionLimitExceededError();
if (!depositAssets.isZero() && depositAssets.lt(context.settlementFee))
revert VaultInsufficientMinimumError();
if (!redeemShares.isZero() && context.latestCheckpoint.toAssets(redeemShares, context.settlementFee).isZero())
revert VaultInsufficientMinimumError();

if (context.local.current != context.local.latest) revert VaultExistingOrderError();

// magic values
Expand Down Expand Up @@ -284,6 +291,7 @@ contract Vault is IVault, Instance {
if (context.global.assets.isZero()) return UFixed6Lib.ZERO;
UFixed6 totalCollateral = UFixed6Lib.from(_collateral(context).max(Fixed6Lib.ZERO));
claimAmount = claimAssets.muldiv(totalCollateral.min(context.global.assets), context.global.assets);

if (depositAssets.isZero() && redeemShares.isZero()) claimAmount = claimAmount.sub(context.settlementFee);
}

Expand Down Expand Up @@ -472,7 +480,7 @@ contract Vault is IVault, Instance {
.muldiv(marketContext.price, registration.leverage) // available collateral
.muldiv(context.totalWeight, registration.weight); // collateral in market

redemptionAmount = redemptionAmount.min(context.latestCheckpoint.toSharesLocal(collateral));
redemptionAmount = redemptionAmount.min(context.latestCheckpoint.toShares(collateral, UFixed6Lib.ZERO));
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/perennial-vault/contracts/interfaces/IVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ interface IVault is IInstance {
error VaultIncorrectAssetError();
error VaultNotOperatorError();
error VaultNotSingleSidedError();
error VaultInsufficientMinimumError();

error AccountStorageInvalidError();
error CheckpointStorageInvalidError();
Expand Down
41 changes: 36 additions & 5 deletions packages/perennial-vault/contracts/types/Checkpoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ library CheckpointLib {
self.fee = fee;
self.keeper = keeper;
}

/// @notice Converts a given amount of assets to shares at checkpoint in the global context
/// @param assets Number of assets to convert to shares
/// @return Amount of shares for the given assets at checkpoint
Expand All @@ -94,7 +94,7 @@ library CheckpointLib {
/// @return Amount of assets for the given shares at checkpoint
function toAssetsGlobal(Checkpoint memory self, UFixed6 shares) internal pure returns (UFixed6) {
// vault is fresh, use par value
return self.shares.isZero() ? shares : _withoutKeeperGlobal(self, _toAssets(self, shares));
return _withoutKeeperGlobal(self, self.shares.isZero() ? shares : _toAssets(self, shares));
}


Expand All @@ -114,7 +114,30 @@ library CheckpointLib {
/// @return Amount of assets for the given shares at checkpoint
function toAssetsLocal(Checkpoint memory self, UFixed6 shares) internal pure returns (UFixed6) {
// vault is fresh, use par value
return self.shares.isZero() ? shares : _withoutKeeperGlobal(self, _toAssets(self, shares));
return _withoutKeeperLocal(self, self.shares.isZero() ? shares : _toAssets(self, shares));
}

/// @notice Converts a given amount of assets to shares at checkpoint in the global context
/// @dev Dev used in limit calculations when a non-historical keeper fee must be used
/// @param assets Number of assets to convert to shares
/// @param keeper Custom keeper fee
/// @return Amount of shares for the given assets at checkpoint
function toShares(Checkpoint memory self, UFixed6 assets, UFixed6 keeper) internal pure returns (UFixed6) {
// vault is fresh, use par value
if (self.shares.isZero()) return assets;

// if vault is insolvent, default to par value
return self.assets.lte(Fixed6Lib.ZERO) ? assets : _toShares(self, _withoutKeeper(assets, keeper));
}

/// @notice Converts a given amount of shares to assets with checkpoint in the global context
/// @dev Dev used in limit calculations when a non-historical keeper fee must be used
/// @param shares Number of shares to convert to shares
/// @param keeper Custom keeper fee
/// @return Amount of assets for the given shares at checkpoint
function toAssets(Checkpoint memory self, UFixed6 shares, UFixed6 keeper) internal pure returns (UFixed6) {
// vault is fresh, use par value
return _withoutKeeper(self.shares.isZero() ? shares : _toAssets(self, shares), keeper);
}

/// @notice Converts a given amount of assets to shares at checkpoint
Expand Down Expand Up @@ -150,7 +173,7 @@ library CheckpointLib {
/// @param amount The amount to apply the fee to
/// @return The amount with the settlement fee
function _withoutKeeperGlobal(Checkpoint memory self, UFixed6 amount) private pure returns (UFixed6) {
return amount.sub(self.keeper.min(amount));
return _withoutKeeper(amount, self.keeper);
}

/// @notice Applies the fixed settlement fee to a given amount in the local context
Expand All @@ -159,7 +182,15 @@ library CheckpointLib {
/// @return The amount with the settlement fee
function _withoutKeeperLocal(Checkpoint memory self, UFixed6 amount) private pure returns (UFixed6) {
UFixed6 keeperPer = self.count == 0 ? UFixed6Lib.ZERO : self.keeper.div(UFixed6Lib.from(self.count));
return amount.sub(keeperPer.min(amount));
return _withoutKeeper(amount, keeperPer);
}

/// @notice Applies the fixed settlement fee to a given amount in the local context
/// @param amount The amount to apply the fee to
/// @param keeper The amount of settlement fee to deduct
/// @return The amount with the settlement fee
function _withoutKeeper(UFixed6 amount, UFixed6 keeper) private pure returns (UFixed6) {
return amount.sub(keeper.min(amount));
}

/// @notice Returns if the checkpoint is healthy
Expand Down
20 changes: 20 additions & 0 deletions packages/perennial-vault/test/integration/vault/Vault.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,26 @@ describe('Vault', () => {
expect(await vault.convertToShares(parse6decimal('1004').add(0))).to.equal(parse6decimal('1000'))
})

it('reverts when below settlement fee', async () => {
const settlementFee = parse6decimal('1.00')
const marketParameter = { ...(await market.parameter()) }
marketParameter.settlementFee = settlementFee
await market.connect(owner).updateParameter(marketParameter)
const btcMarketParameter = { ...(await btcMarket.parameter()) }
btcMarketParameter.settlementFee = settlementFee
await btcMarket.connect(owner).updateParameter(btcMarketParameter)

await expect(vault.connect(user).update(user.address, parse6decimal('0.50'), 0, 0)).to.revertedWithCustomError(
vault,
'VaultInsufficientMinimumError',
)
await expect(vault.connect(user).update(user.address, 0, parse6decimal('0.50'), 0)).to.revertedWithCustomError(
vault,
'VaultInsufficientMinimumError',
)
await expect(vault.connect(user).update(user.address, 0, 0, parse6decimal('0.50'))).to.revertedWithPanic('0x11')
})

it('reverts when paused', async () => {
await vaultFactory.connect(owner).pause()
await expect(vault.settle(user.address)).to.revertedWithCustomError(vault, 'InstancePausedError')
Expand Down

0 comments on commit 4852cfe

Please sign in to comment.