Skip to content

Commit

Permalink
Merge pull request #466 from equilibria-xyz/britz-fix-price-override-…
Browse files Browse the repository at this point in the history
…invariant

Fix margin check to incorporate price override
  • Loading branch information
kbrizzle authored Oct 9, 2024
2 parents 10e0cdd + 61d6646 commit 4424431
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 3 deletions.
2 changes: 1 addition & 1 deletion packages/perennial/contracts/libs/CheckpointLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,6 @@ library CheckpointLib {
Version memory toVersion
) private pure returns (Fixed6) {
if (!toVersion.valid) return Fixed6Lib.ZERO;
return guarantee.taker().mul(toVersion.price).sub(guarantee.notional);
return guarantee.priceAdjustment(toVersion.price);
}
}
2 changes: 1 addition & 1 deletion packages/perennial/contracts/libs/InvariantLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ library InvariantLib {
context.latestOracleVersion,
context.riskParameter,
updateContext.collateralization,
context.local.collateral
context.local.collateral.add(newGuarantee.priceAdjustment(context.latestOracleVersion.price)) // apply price override adjustment from intent if present
)
) revert IMarket.MarketInsufficientMarginError();

Expand Down
6 changes: 5 additions & 1 deletion packages/perennial/contracts/test/GuaranteeTester.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ abstract contract GuaranteeTester {

function takerTotal(Guarantee memory guarantee) public pure returns (UFixed6) {
return GuaranteeLib.takerTotal(guarantee);
}
}

function priceAdjustment(Guarantee memory guarantee, Fixed6 price) public pure returns (Fixed6) {
return GuaranteeLib.priceAdjustment(guarantee, price);
}
}

contract GuaranteeGlobalTester is GuaranteeTester {
Expand Down
8 changes: 8 additions & 0 deletions packages/perennial/contracts/types/Guarantee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ library GuaranteeLib {
return self.takerPos.add(self.takerNeg);
}

/// @notice Returns the collateral adjusted due to the price override
/// @param self The guarantee object to check
/// @param price The oracle price to compare to the price override
/// @return The collateral adjusted due to the price override
function priceAdjustment(Guarantee memory self, Fixed6 price) internal pure returns (Fixed6) {
return self.taker().mul(price).sub(self.notional);
}

/// @notice Updates the current global guarantee with a new local guarantee
/// @param self The guarantee object to update
/// @param guarantee The new guarantee
Expand Down
116 changes: 116 additions & 0 deletions packages/perennial/test/unit/market/Market.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14712,6 +14712,122 @@ describe('Market', () => {
).to.be.revertedWithCustomError(market, 'MarketInsufficientMarginError')
})

it('reverts if under margin (intent maker)', async () => {
const intent = {
amount: POSITION.div(2),
price: parse6decimal('1250'),
fee: parse6decimal('0.5'),
originator: liquidator.address,
solver: owner.address,
collateralization: parse6decimal('0.01'),
common: {
account: user.address,
signer: user.address,
domain: market.address,
nonce: 0,
group: 0,
expiry: 0,
},
}

const LOWER_COLLATERAL = parse6decimal('500')

dsu.transferFrom.whenCalledWith(user.address, market.address, LOWER_COLLATERAL.mul(1e12)).returns(true)
dsu.transferFrom.whenCalledWith(userB.address, market.address, LOWER_COLLATERAL.mul(1e12)).returns(true)
dsu.transferFrom.whenCalledWith(userC.address, market.address, LOWER_COLLATERAL.mul(1e12)).returns(true)

await market
.connect(userB)
['update(address,uint256,uint256,uint256,int256,bool)'](
userB.address,
POSITION,
0,
0,
LOWER_COLLATERAL,
false,
)

await market
.connect(user)
['update(address,uint256,uint256,uint256,int256,bool)'](user.address, 0, 0, 0, LOWER_COLLATERAL, false)
await market
.connect(userC)
['update(address,uint256,uint256,uint256,int256,bool)'](userC.address, 0, 0, 0, LOWER_COLLATERAL, false)

verifier.verifyIntent.returns()

// taker
factory.authorization
.whenCalledWith(user.address, userC.address, user.address, liquidator.address)
.returns([false, true, parse6decimal('0.20')])

await expect(
market
.connect(userC)
[
'update(address,(int256,int256,uint256,address,address,uint256,(address,address,address,uint256,uint256,uint256)),bytes)'
](userC.address, intent, DEFAULT_SIGNATURE),
).to.be.revertedWithCustomError(market, 'MarketInsufficientMarginError')
})

it('reverts if under margin (intent taker)', async () => {
const intent = {
amount: POSITION.div(2),
price: parse6decimal('25'),
fee: parse6decimal('0.5'),
originator: liquidator.address,
solver: owner.address,
collateralization: parse6decimal('0.01'),
common: {
account: user.address,
signer: user.address,
domain: market.address,
nonce: 0,
group: 0,
expiry: 0,
},
}

const LOWER_COLLATERAL = parse6decimal('500')

dsu.transferFrom.whenCalledWith(user.address, market.address, LOWER_COLLATERAL.mul(1e12)).returns(true)
dsu.transferFrom.whenCalledWith(userB.address, market.address, LOWER_COLLATERAL.mul(1e12)).returns(true)
dsu.transferFrom.whenCalledWith(userC.address, market.address, LOWER_COLLATERAL.mul(1e12)).returns(true)

await market
.connect(userB)
['update(address,uint256,uint256,uint256,int256,bool)'](
userB.address,
POSITION,
0,
0,
LOWER_COLLATERAL,
false,
)

await market
.connect(user)
['update(address,uint256,uint256,uint256,int256,bool)'](user.address, 0, 0, 0, LOWER_COLLATERAL, false)
await market
.connect(userC)
['update(address,uint256,uint256,uint256,int256,bool)'](userC.address, 0, 0, 0, LOWER_COLLATERAL, false)

verifier.verifyIntent.returns()

// taker
factory.authorization
.whenCalledWith(user.address, userC.address, user.address, liquidator.address)
.returns([false, true, parse6decimal('0.20')])

await expect(
market
.connect(userC)
[
'update(address,(int256,int256,uint256,address,address,uint256,(address,address,address,uint256,uint256,uint256)),bytes)'
](userC.address, intent, DEFAULT_SIGNATURE),
).to.be.revertedWithCustomError(market, 'MarketInsufficientMarginError')
})

it('reverts if paused (market)', async () => {
factory.paused.returns(true)
await expect(
Expand Down
78 changes: 78 additions & 0 deletions packages/perennial/test/unit/types/Guarantee.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,84 @@ describe('Guarantee', () => {
).to.equal(7)
})
})

describe('#priceAdjustment', () => {
it('long / higher price', async () => {
await expect(
await guaranteeLocal.priceAdjustment(
{
...DEFAULT_GUARANTEE,
notional: parse6decimal('1230'),
takerPos: parse6decimal('10'),
},
parse6decimal('125'),
),
).to.equal(parse6decimal('20'))
})

it('short / lower price', async () => {
await expect(
await guaranteeLocal.priceAdjustment(
{
...DEFAULT_GUARANTEE,
notional: parse6decimal('-1230'),
takerNeg: parse6decimal('10'),
},
parse6decimal('121'),
),
).to.equal(parse6decimal('20'))
})

it('long / lower price', async () => {
await expect(
await guaranteeLocal.priceAdjustment(
{
...DEFAULT_GUARANTEE,
notional: parse6decimal('1230'),
takerPos: parse6decimal('10'),
},
parse6decimal('121'),
),
).to.equal(parse6decimal('-20'))
})

it('short / higher price', async () => {
await expect(
await guaranteeLocal.priceAdjustment(
{
...DEFAULT_GUARANTEE,
notional: parse6decimal('-1230'),
takerNeg: parse6decimal('10'),
},
parse6decimal('125'),
),
).to.equal(parse6decimal('-20'))
})

it('zero price', async () => {
await expect(
await guaranteeLocal.priceAdjustment(
{
...DEFAULT_GUARANTEE,
notional: parse6decimal('0'),
takerNeg: parse6decimal('10'),
},
parse6decimal('121'),
),
).to.equal(parse6decimal('-1210'))
})

it('zero size', async () => {
await expect(
await guaranteeLocal.priceAdjustment(
{
...DEFAULT_GUARANTEE,
},
parse6decimal('121'),
),
).to.equal(parse6decimal('0'))
})
})
})

function shouldBehaveLike(
Expand Down

0 comments on commit 4424431

Please sign in to comment.