From b6a952ee845ab7041c98a0f2b6f22138bac763d6 Mon Sep 17 00:00:00 2001 From: sherlock-admin Date: Thu, 28 Nov 2024 14:03:20 +0000 Subject: [PATCH 1/2] Fix Review --- gl-sherlock/contracts/api.vy | 22 ++++++++++++++-------- gl-sherlock/contracts/math.vy | 2 +- gl-sherlock/contracts/params.vy | 4 ++-- gl-sherlock/contracts/positions.vy | 19 ++++++++++++++----- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/gl-sherlock/contracts/api.vy b/gl-sherlock/contracts/api.vy index 6900984..6d3f907 100644 --- a/gl-sherlock/contracts/api.vy +++ b/gl-sherlock/contracts/api.vy @@ -4,6 +4,7 @@ import params as Params import pools as Pools import fees as Fees import positions as Positions +import ERC20Plus as ERC20Plus ######################################################################## # This is the entry-point contract. @@ -55,10 +56,10 @@ def CONTEXT( quote_token: address, desired : uint256, slippage : uint256, - payload : Bytes[224] + payload : Bytes[512] ) -> Ctx: - base_decimals : uint256 = convert(ERC20Plus(base_token).decimals(), uint256) - quote_decimals: uint256 = convert(ERC20Plus(quote_token).decimals(), uint256) + base_decimals : uint256 = ERC20Plus(base_token).decimals() + quote_decimals: uint256 = ERC20Plus(quote_token).decimals() # this will revert on error price : uint256 = self.ORACLE.price(quote_decimals, desired, @@ -72,6 +73,7 @@ def CONTEXT( ######################################################################## @external +@nonreentrant("lock") def mint( base_token : address, #ERC20 quote_token : address, #ERC20 @@ -80,7 +82,7 @@ def mint( quote_amt : uint256, desired : uint256, slippage : uint256, - payload : Bytes[224] + payload : Bytes[512] ) -> uint256: """ @notice Provide liquidity to the pool @@ -101,6 +103,7 @@ def mint( return self.CORE.mint(1, base_token, quote_token, lp_token, base_amt, quote_amt, ctx) @external +@nonreentrant("lock") def burn( base_token : address, quote_token : address, @@ -108,7 +111,7 @@ def burn( lp_amt : uint256, desired : uint256, slippage : uint256, - payload : Bytes[224] + payload : Bytes[512] ) -> Tokens: """ @notice Withdraw liquidity from the pool @@ -128,6 +131,7 @@ def burn( return self.CORE.burn(1, base_token, quote_token, lp_token, lp_amt, ctx) @external +@nonreentrant("lock") def open( base_token : address, quote_token : address, @@ -136,7 +140,7 @@ def open( leverage : uint256, desired : uint256, slippage : uint256, - payload : Bytes[224] + payload : Bytes[512] ) -> PositionState: """ @notice Open a position @@ -158,13 +162,14 @@ def open( return self.CORE.open(1, base_token, quote_token, long, collateral0, leverage, ctx) @external +@nonreentrant("lock") def close( base_token : address, quote_token : address, position_id : uint256, desired : uint256, slippage : uint256, - payload : Bytes[224] + payload : Bytes[512] ) -> PositionValue: """ @notice Close a position @@ -183,13 +188,14 @@ def close( return self.CORE.close(1, base_token, quote_token, position_id, ctx) @external +@nonreentrant("lock") def liquidate( base_token : address, quote_token: address, position_id: uint256, desired : uint256, slippage : uint256, - payload : Bytes[224] + payload : Bytes[512] ) -> PositionValue: """ @notice Liquidate a position diff --git a/gl-sherlock/contracts/math.vy b/gl-sherlock/contracts/math.vy index f16c1ae..4066e0e 100644 --- a/gl-sherlock/contracts/math.vy +++ b/gl-sherlock/contracts/math.vy @@ -160,7 +160,7 @@ def balanced(state: Value, burn_value: uint256, ctx: Ctx) -> Tokens: # Magic number which depends on the smallest fee one wants to support # and the blocktime (since fees are per block). See types.vy/Parameters # for an example. -DENOM: constant(uint256) = 1_000_000_000 +DENOM: constant(uint256) = 1_000_000_000_000 @external @pure diff --git a/gl-sherlock/contracts/params.vy b/gl-sherlock/contracts/params.vy index f3c5366..388dbc4 100644 --- a/gl-sherlock/contracts/params.vy +++ b/gl-sherlock/contracts/params.vy @@ -60,12 +60,12 @@ def utilization(reserves: uint256, interest: uint256) -> uint256: """ Reserve utilization in percent (rounded down). """ - return 0 if (reserves == 0 or interest == 0) else (interest / (reserves / 100)) + return 0 if (reserves == 0 or interest == 0) else (interest / (reserves / 100_000)) @internal @pure def scale(fee: uint256, utilization: uint256) -> uint256: - return (fee * utilization) / 100 + return (fee * utilization) / 100_000 @internal @view diff --git a/gl-sherlock/contracts/positions.vy b/gl-sherlock/contracts/positions.vy index 1796f96..8e3c426 100644 --- a/gl-sherlock/contracts/positions.vy +++ b/gl-sherlock/contracts/positions.vy @@ -192,6 +192,10 @@ def value(id: uint256, ctx: Ctx) -> PositionValue: # # Positions which go negative due to price fluctuations cost the pool # EV profits since the most it can make is available collateral. + pool : PoolState = self.POOLS.lookup(pos.pool) + bc : uint256 = pool.base_collateral + qc : uint256 = pool.quote_collateral + deltas: Deltas = Deltas({ base_interest : [self.MATH.MINUS(pos.interest)], quote_interest : [], @@ -206,19 +210,19 @@ def value(id: uint256, ctx: Ctx) -> PositionValue: quote_transfer : [], # in the worst case describe above, reserves # dont change - quote_reserves : [self.MATH.PLUS(pos.collateral), #does not need min() + quote_reserves : [self.MATH.PLUS(min(pos.collateral, qc)), self.MATH.MINUS(fees.funding_paid)], quote_collateral: [self.MATH.PLUS(fees.funding_paid), - self.MATH.MINUS(pos.collateral)], + self.MATH.MINUS(min(pos.collateral, qc))], }) if pos.long else Deltas({ base_interest : [], quote_interest : [self.MATH.MINUS(pos.interest)], base_transfer : [], - base_reserves : [self.MATH.PLUS(pos.collateral), + base_reserves : [self.MATH.PLUS(min(pos.collateral, bc)), self.MATH.MINUS(fees.funding_paid)], base_collateral : [self.MATH.PLUS(fees.funding_paid), # <- - self.MATH.MINUS(pos.collateral)], + self.MATH.MINUS(min(pos.collateral, bc))], quote_transfer : [self.MATH.PLUS(pnl.payout), self.MATH.PLUS(fees.funding_received)], @@ -250,8 +254,13 @@ def calc_fees(id: uint256) -> FeesPaid: c1 : Val = self.deduct(c0, fees.funding_paid) c2 : Val = self.deduct(c1.remaining, fees.borrowing_paid) # Funding fees prioritized over borrowing fees. - funding_paid : uint256 = c1.deducted + avail_pay : uint256 = pool.quote_collateral if pos.long else ( + pool.base_collateral ) + funding_paid : uint256 = min(c1.deducted, avail_pay) + # borrowing_paid is for informational purposes only, could also say + # min(c2.deducted, avail_pay - funding_paid). borrowing_paid : uint256 = c2.deducted + # other users' bad positions do not affect liquidatability remaining : uint256 = c2.remaining # When there are negative positions (liquidation bot failure): avail : uint256 = pool.base_collateral if pos.long else ( From caf3f9d95e6c18e735d117f59899a2c0feacad42 Mon Sep 17 00:00:00 2001 From: sherlock-admin Date: Wed, 8 Jan 2025 11:43:23 +0000 Subject: [PATCH 2/2] Fix Review --- gl-sherlock/contracts/api.vy | 1 + gl-sherlock/contracts/api.vy.rej | 139 +++++++++++++++++++++++++ gl-sherlock/contracts/math.vy.rej | 10 ++ gl-sherlock/contracts/params.vy.rej | 16 +++ gl-sherlock/contracts/positions.vy.rej | 51 +++++++++ 5 files changed, 217 insertions(+) create mode 100644 gl-sherlock/contracts/api.vy.rej create mode 100644 gl-sherlock/contracts/math.vy.rej create mode 100644 gl-sherlock/contracts/params.vy.rej create mode 100644 gl-sherlock/contracts/positions.vy.rej diff --git a/gl-sherlock/contracts/api.vy b/gl-sherlock/contracts/api.vy index 6d3f907..fb8c1cb 100644 --- a/gl-sherlock/contracts/api.vy +++ b/gl-sherlock/contracts/api.vy @@ -30,6 +30,7 @@ CORE : public(Core) DEPLOYER : address INITIALIZED: bool +LOCK : public(HashMap[address, uint256]) @external def __init__(): diff --git a/gl-sherlock/contracts/api.vy.rej b/gl-sherlock/contracts/api.vy.rej new file mode 100644 index 0000000..1323536 --- /dev/null +++ b/gl-sherlock/contracts/api.vy.rej @@ -0,0 +1,139 @@ +diff a/gl-sherlock/contracts/api.vy b/gl-sherlock/contracts/api.vy (rejected hunks) +@@ -4,6 +4,7 @@ import params as Params + import pools as Pools + import fees as Fees + import positions as Positions ++import ERC20Plus as ERC20Plus + + ######################################################################## + # This is the entry-point contract. +@@ -55,10 +57,10 @@ def CONTEXT( + quote_token: address, + desired : uint256, + slippage : uint256, +- payload : Bytes[224] ++ payload : Bytes[512] + ) -> Ctx: +- base_decimals : uint256 = convert(ERC20Plus(base_token).decimals(), uint256) +- quote_decimals: uint256 = convert(ERC20Plus(quote_token).decimals(), uint256) ++ base_decimals : uint256 = ERC20Plus(base_token).decimals() ++ quote_decimals: uint256 = ERC20Plus(quote_token).decimals() + # this will revert on error + price : uint256 = self.ORACLE.price(quote_decimals, + desired, +@@ -70,8 +72,20 @@ def CONTEXT( + quote_decimals: quote_decimals, + }) + ++ERR_LOCK: constant(String[16]) = "LOCKED" ++ ++@internal ++def is_locked() -> bool: ++ return self.LOCK[tx.origin] == block.number ++ ++@internal ++def lock(): ++ assert not self.is_locked(), ERR_LOCK ++ self.LOCK[tx.origin] = block.number ++ + ######################################################################## + @external ++@nonreentrant("lock") + def mint( + base_token : address, #ERC20 + quote_token : address, #ERC20 +@@ -80,7 +94,7 @@ def mint( + quote_amt : uint256, + desired : uint256, + slippage : uint256, +- payload : Bytes[224] ++ payload : Bytes[512] + ) -> uint256: + """ + @notice Provide liquidity to the pool +@@ -97,10 +111,12 @@ def mint( + send 50000). + @param payload Signed Redstone oracle payload + """ ++ self.lock() + ctx: Ctx = self.CONTEXT(base_token, quote_token, desired, slippage, payload) + return self.CORE.mint(1, base_token, quote_token, lp_token, base_amt, quote_amt, ctx) + + @external ++@nonreentrant("lock") + def burn( + base_token : address, + quote_token : address, +@@ -108,7 +124,7 @@ def burn( + lp_amt : uint256, + desired : uint256, + slippage : uint256, +- payload : Bytes[224] ++ payload : Bytes[512] + ) -> Tokens: + """ + @notice Withdraw liquidity from the pool +@@ -124,10 +140,12 @@ def burn( + send 50000). + @param payload Signed Redstone oracle payload + """ ++ self.lock() + ctx: Ctx = self.CONTEXT(base_token, quote_token, desired, slippage, payload) + return self.CORE.burn(1, base_token, quote_token, lp_token, lp_amt, ctx) + + @external ++@nonreentrant("lock") + def open( + base_token : address, + quote_token : address, +@@ -136,7 +154,7 @@ def open( + leverage : uint256, + desired : uint256, + slippage : uint256, +- payload : Bytes[224] ++ payload : Bytes[512] + ) -> PositionState: + """ + @notice Open a position +@@ -154,17 +172,19 @@ def open( + send 50000). + @param payload Signed Redstone oracle payload + """ ++ self.lock() + ctx: Ctx = self.CONTEXT(base_token, quote_token, desired, slippage, payload) + return self.CORE.open(1, base_token, quote_token, long, collateral0, leverage, ctx) + + @external ++@nonreentrant("lock") + def close( + base_token : address, + quote_token : address, + position_id : uint256, + desired : uint256, + slippage : uint256, +- payload : Bytes[224] ++ payload : Bytes[512] + ) -> PositionValue: + """ + @notice Close a position +@@ -179,17 +199,19 @@ def close( + send 50000). + @param payload Signed Redstone oracle payload + """ ++ self.lock() + ctx: Ctx = self.CONTEXT(base_token, quote_token, desired, slippage, payload) + return self.CORE.close(1, base_token, quote_token, position_id, ctx) + + @external ++@nonreentrant("lock") + def liquidate( + base_token : address, + quote_token: address, + position_id: uint256, + desired : uint256, + slippage : uint256, +- payload : Bytes[224] ++ payload : Bytes[512] + ) -> PositionValue: + """ + @notice Liquidate a position diff --git a/gl-sherlock/contracts/math.vy.rej b/gl-sherlock/contracts/math.vy.rej new file mode 100644 index 0000000..5859f6a --- /dev/null +++ b/gl-sherlock/contracts/math.vy.rej @@ -0,0 +1,10 @@ +diff a/gl-sherlock/contracts/math.vy b/gl-sherlock/contracts/math.vy (rejected hunks) +@@ -160,7 +160,7 @@ def balanced(state: Value, burn_value: uint256, ctx: Ctx) -> Tokens: + # Magic number which depends on the smallest fee one wants to support + # and the blocktime (since fees are per block). See types.vy/Parameters + # for an example. +-DENOM: constant(uint256) = 1_000_000_000 ++DENOM: constant(uint256) = 1_000_000_000_000 + + @external + @pure diff --git a/gl-sherlock/contracts/params.vy.rej b/gl-sherlock/contracts/params.vy.rej new file mode 100644 index 0000000..76ff229 --- /dev/null +++ b/gl-sherlock/contracts/params.vy.rej @@ -0,0 +1,16 @@ +diff a/gl-sherlock/contracts/params.vy b/gl-sherlock/contracts/params.vy (rejected hunks) +@@ -60,12 +60,12 @@ def utilization(reserves: uint256, interest: uint256) -> uint256: + """ + Reserve utilization in percent (rounded down). + """ +- return 0 if (reserves == 0 or interest == 0) else (interest / (reserves / 100)) ++ return 0 if (reserves == 0 or interest == 0) else (interest / (reserves / 100_000)) + + @internal + @pure + def scale(fee: uint256, utilization: uint256) -> uint256: +- return (fee * utilization) / 100 ++ return (fee * utilization) / 100_000 + + @internal + @view diff --git a/gl-sherlock/contracts/positions.vy.rej b/gl-sherlock/contracts/positions.vy.rej new file mode 100644 index 0000000..c8b9345 --- /dev/null +++ b/gl-sherlock/contracts/positions.vy.rej @@ -0,0 +1,51 @@ +diff a/gl-sherlock/contracts/positions.vy b/gl-sherlock/contracts/positions.vy (rejected hunks) +@@ -192,6 +192,10 @@ def value(id: uint256, ctx: Ctx) -> PositionValue: + # + # Positions which go negative due to price fluctuations cost the pool + # EV profits since the most it can make is available collateral. ++ pool : PoolState = self.POOLS.lookup(pos.pool) ++ bc : uint256 = pool.base_collateral ++ qc : uint256 = pool.quote_collateral ++ + deltas: Deltas = Deltas({ + base_interest : [self.MATH.MINUS(pos.interest)], + quote_interest : [], +@@ -206,19 +210,19 @@ def value(id: uint256, ctx: Ctx) -> PositionValue: + quote_transfer : [], + # in the worst case describe above, reserves + # dont change +- quote_reserves : [self.MATH.PLUS(pos.collateral), #does not need min() ++ quote_reserves : [self.MATH.PLUS(min(pos.collateral, qc)), + self.MATH.MINUS(fees.funding_paid)], + quote_collateral: [self.MATH.PLUS(fees.funding_paid), +- self.MATH.MINUS(pos.collateral)], ++ self.MATH.MINUS(min(pos.collateral, qc))], + }) if pos.long else Deltas({ + base_interest : [], + quote_interest : [self.MATH.MINUS(pos.interest)], + + base_transfer : [], +- base_reserves : [self.MATH.PLUS(pos.collateral), ++ base_reserves : [self.MATH.PLUS(min(pos.collateral, bc)), + self.MATH.MINUS(fees.funding_paid)], + base_collateral : [self.MATH.PLUS(fees.funding_paid), # <- +- self.MATH.MINUS(pos.collateral)], ++ self.MATH.MINUS(min(pos.collateral, bc))], + + quote_transfer : [self.MATH.PLUS(pnl.payout), + self.MATH.PLUS(fees.funding_received)], +@@ -250,8 +254,13 @@ def calc_fees(id: uint256) -> FeesPaid: + c1 : Val = self.deduct(c0, fees.funding_paid) + c2 : Val = self.deduct(c1.remaining, fees.borrowing_paid) + # Funding fees prioritized over borrowing fees. +- funding_paid : uint256 = c1.deducted ++ avail_pay : uint256 = pool.quote_collateral if pos.long else ( ++ pool.base_collateral ) ++ funding_paid : uint256 = min(c1.deducted, avail_pay) ++ # borrowing_paid is for informational purposes only, could also say ++ # min(c2.deducted, avail_pay - funding_paid). + borrowing_paid : uint256 = c2.deducted ++ # other users' bad positions do not affect liquidatability + remaining : uint256 = c2.remaining + # When there are negative positions (liquidation bot failure): + avail : uint256 = pool.base_collateral if pos.long else (