From 3bcf126e38be9c9c49970d579a42ae2cf43d375b Mon Sep 17 00:00:00 2001 From: "prd-carapulse[bot]" <264278285+prd-carapulse[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:22:53 +0000 Subject: [PATCH 01/18] fix: emit signer in ecrecover authorizer event Include the recovered signer in the EcrecoverAuthorizer SetIsAuthorized event so offchain consumers can reconstruct who signed delegated authorizations. --- src/periphery/EcrecoverAuthorizer.sol | 3 ++- .../interfaces/IEcrecoverAuthorizer.sol | 7 +++++- test/SetIsAuthorizedWithSigTest.sol | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/periphery/EcrecoverAuthorizer.sol b/src/periphery/EcrecoverAuthorizer.sol index 916e05a44..e395f58f5 100644 --- a/src/periphery/EcrecoverAuthorizer.sol +++ b/src/periphery/EcrecoverAuthorizer.sol @@ -40,7 +40,8 @@ contract EcrecoverAuthorizer is IEcrecoverAuthorizer { authorization.authorizer, authorization.authorized, authorization.isAuthorized, - authorization.nonce + authorization.nonce, + signer ); IMidnight(MIDNIGHT) diff --git a/src/periphery/interfaces/IEcrecoverAuthorizer.sol b/src/periphery/interfaces/IEcrecoverAuthorizer.sol index e20e94f2c..38bf60063 100644 --- a/src/periphery/interfaces/IEcrecoverAuthorizer.sol +++ b/src/periphery/interfaces/IEcrecoverAuthorizer.sol @@ -32,7 +32,12 @@ interface IEcrecoverAuthorizer { /// EVENTS /// event SetIsAuthorized( - address indexed caller, address indexed authorizer, address indexed authorized, bool isAuthorized, uint256 nonce + address indexed caller, + address indexed authorizer, + address indexed authorized, + bool isAuthorized, + uint256 nonce, + address signer ); /// STORAGE GETTERS /// diff --git a/test/SetIsAuthorizedWithSigTest.sol b/test/SetIsAuthorizedWithSigTest.sol index 6e233a1ce..0a0e9c5a9 100644 --- a/test/SetIsAuthorizedWithSigTest.sol +++ b/test/SetIsAuthorizedWithSigTest.sol @@ -16,6 +16,15 @@ bytes constant AUTHORIZATION_TYPE = bytes constant EIP712_DOMAIN_TYPE = "EIP712Domain(uint256 chainId,address verifyingContract)"; contract EcrecoverAuthorizerTest is BaseTest { + event SetIsAuthorized( + address indexed caller, + address indexed authorizer, + address indexed authorized, + bool isAuthorized, + uint256 nonce, + address signer + ); + function testAuthorizationTypeHash() public pure { assertEq(AUTHORIZATION_TYPEHASH, keccak256(AUTHORIZATION_TYPE)); } @@ -85,6 +94,20 @@ contract EcrecoverAuthorizerTest is BaseTest { assertEq(ecrecoverAuthorizer.nonce(borrower), 1); } + function testEcrecoverAuthorizerEventEmitsSigner() public { + vm.startPrank(borrower); + midnight.setIsAuthorized(address(ecrecoverAuthorizer), true, borrower); + midnight.setIsAuthorized(otherBorrower, true, borrower); + vm.stopPrank(); + + Authorization memory auth = makeAuthorization(borrower, lender, true); + Signature memory sig = signAuthorization(auth, otherBorrower); + + vm.expectEmit(true, true, true, true, address(ecrecoverAuthorizer)); + emit SetIsAuthorized(address(this), borrower, lender, true, auth.nonce, otherBorrower); + ecrecoverAuthorizer.setIsAuthorized(auth, sig); + } + function testEcrecoverAuthorizerInvalidSignature() public { Authorization memory auth = makeAuthorization(borrower, lender, true); Signature memory sig = signAuthorization(auth, lender); // wrong signer From ada5c3a3a6954abd3bac0faafc8acaaffd10af48 Mon Sep 17 00:00:00 2001 From: "prd-carapulse[bot]" <264278285+prd-carapulse[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:34:27 +0000 Subject: [PATCH 02/18] test: simplify ecrecover authorizer event expectation Use the no-argument Foundry expectEmit helper in the signer event regression test. --- test/SetIsAuthorizedWithSigTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SetIsAuthorizedWithSigTest.sol b/test/SetIsAuthorizedWithSigTest.sol index 0a0e9c5a9..ab31dff35 100644 --- a/test/SetIsAuthorizedWithSigTest.sol +++ b/test/SetIsAuthorizedWithSigTest.sol @@ -103,7 +103,7 @@ contract EcrecoverAuthorizerTest is BaseTest { Authorization memory auth = makeAuthorization(borrower, lender, true); Signature memory sig = signAuthorization(auth, otherBorrower); - vm.expectEmit(true, true, true, true, address(ecrecoverAuthorizer)); + vm.expectEmit(); emit SetIsAuthorized(address(this), borrower, lender, true, auth.nonce, otherBorrower); ecrecoverAuthorizer.setIsAuthorized(auth, sig); } From 18194ab2ab221a850f59e4e8ae6b8736db4db2f1 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Tue, 2 Jun 2026 15:17:04 +0200 Subject: [PATCH 03/18] reconstruct calls --- src/Midnight.sol | 15 +++++++++++++-- src/libraries/EventsLib.sol | 6 +++--- test/ContinuousFeeTest.sol | 12 +++++++++++- test/LiquidationTest.sol | 1 + 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Midnight.sol b/src/Midnight.sol index 32c8c71f7..855fca9f7 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -430,6 +430,12 @@ contract Midnight is IMidnight { offer.maker, offer.buy, offer.group, + offer.tick, + offer.start, + offer.expiry, + offer.reduceOnly, + offer.maxUnits, + offer.maxAssets, buyerAssets, sellerAssets, newConsumed, @@ -438,7 +444,11 @@ contract Midnight is IMidnight { buyerCreditIncrease, sellerCreditDecrease, receiver, - payer + offer.receiverIfMakerIsSeller, + receiverIfTakerIsSeller, + payer, + buyerCallback, + sellerCallback ); bool wasLocked = UtilsLib.tExchange(LIQUIDATION_LOCK_SLOT, id, seller, true); @@ -509,7 +519,7 @@ contract Midnight is IMidnight { marketState[id].withdrawable += UtilsLib.toUint128(units); address payer = callback != address(0) ? callback : msg.sender; - emit EventsLib.Repay(msg.sender, id, units, onBehalf, payer); + emit EventsLib.Repay(msg.sender, id, units, onBehalf, payer, callback); if (callback != address(0)) { require( @@ -688,6 +698,7 @@ contract Midnight is IMidnight { postMaturityMode, receiver, payer, + callback, badDebt, _marketState.lossFactor, _marketState.continuousFeeCredit diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index ecdbc794e..4fb4897ae 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -19,12 +19,12 @@ library EventsLib { event SetDefaultContinuousFee(address indexed loanToken, uint256 newContinuousFee); event UpdatePosition(bytes32 indexed id_, address indexed user, uint256 creditDecrease, uint256 pendingFeeDecrease, uint256 accruedFee); event MarketCreated(Market market, bytes32 indexed id_); - event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, address indexed maker, bool offerIsBuy, bytes32 group, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer); + event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, address indexed maker, bool offerIsBuy, bytes32 group, uint256 tick, uint256 start, uint256 expiry, bool reduceOnly, uint256 maxUnits, uint256 maxAssets, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address receiverIfMakerIsSeller, address receiverIfTakerIsSeller, address payer, address buyerCallback, address sellerCallback); event Withdraw(address caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address indexed receiver, uint256 pendingFeeDecrease); - event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer); + event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer, address callback); event SupplyCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf); event WithdrawCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf, address receiver); - event Liquidate(address caller, bytes32 indexed id_, address indexed collateral, uint256 seizedAssets, uint256 repaidUnits, address indexed borrower, bool postMaturityMode, address receiver, address payer, uint256 badDebt, uint256 latestLossFactor, uint256 latestContinuousFeeCredit); + event Liquidate(address caller, bytes32 indexed id_, address indexed collateral, uint256 seizedAssets, uint256 repaidUnits, address indexed borrower, bool postMaturityMode, address receiver, address payer, address callback, uint256 badDebt, uint256 latestLossFactor, uint256 latestContinuousFeeCredit); event SetConsumed(address indexed caller, bytes32 indexed group, uint256 amount, address indexed onBehalf); event FlashLoan(address indexed caller, address[] tokens, uint256[] assets, address indexed callback); event SetIsAuthorized(address indexed caller, address indexed authorized, bool newIsAuthorized, address indexed onBehalf); diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 4536038cd..2ca94a1fc 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -283,6 +283,12 @@ contract ContinuousFeeTest is BaseTest { otherLender, true, keccak256("lender-exit"), + MAX_TICK, + 0, + vm.getBlockTimestamp(), + false, + exitAmount, + 0, takeAssets, takeAssets, exitAmount, @@ -291,7 +297,11 @@ contract ContinuousFeeTest is BaseTest { exitAmount, exitAmount, lender, - otherLender + address(0), + lender, + otherLender, + address(0), + address(0) ); take(exitAmount, lender, _makeBuyOffer(exitAmount, keccak256("lender-exit"))); // lender is taker = seller diff --git a/test/LiquidationTest.sol b/test/LiquidationTest.sol index e2800d706..8cc037a84 100644 --- a/test/LiquidationTest.sol +++ b/test/LiquidationTest.sol @@ -395,6 +395,7 @@ contract LiquidationTest is BaseTest { false, address(this), address(this), + address(0), expectedBadDebt, expectedLossFactor, expectedContinuousFeeCredit From 71b6b4039f78d0e324ba9dd5782b7df7d810d03f Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Tue, 2 Jun 2026 15:31:35 +0200 Subject: [PATCH 04/18] Revert "reconstruct calls" This reverts commit 18194ab2ab221a850f59e4e8ae6b8736db4db2f1. --- src/Midnight.sol | 15 ++------------- src/libraries/EventsLib.sol | 6 +++--- test/ContinuousFeeTest.sol | 12 +----------- test/LiquidationTest.sol | 1 - 4 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src/Midnight.sol b/src/Midnight.sol index 855fca9f7..32c8c71f7 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -430,12 +430,6 @@ contract Midnight is IMidnight { offer.maker, offer.buy, offer.group, - offer.tick, - offer.start, - offer.expiry, - offer.reduceOnly, - offer.maxUnits, - offer.maxAssets, buyerAssets, sellerAssets, newConsumed, @@ -444,11 +438,7 @@ contract Midnight is IMidnight { buyerCreditIncrease, sellerCreditDecrease, receiver, - offer.receiverIfMakerIsSeller, - receiverIfTakerIsSeller, - payer, - buyerCallback, - sellerCallback + payer ); bool wasLocked = UtilsLib.tExchange(LIQUIDATION_LOCK_SLOT, id, seller, true); @@ -519,7 +509,7 @@ contract Midnight is IMidnight { marketState[id].withdrawable += UtilsLib.toUint128(units); address payer = callback != address(0) ? callback : msg.sender; - emit EventsLib.Repay(msg.sender, id, units, onBehalf, payer, callback); + emit EventsLib.Repay(msg.sender, id, units, onBehalf, payer); if (callback != address(0)) { require( @@ -698,7 +688,6 @@ contract Midnight is IMidnight { postMaturityMode, receiver, payer, - callback, badDebt, _marketState.lossFactor, _marketState.continuousFeeCredit diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 4fb4897ae..ecdbc794e 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -19,12 +19,12 @@ library EventsLib { event SetDefaultContinuousFee(address indexed loanToken, uint256 newContinuousFee); event UpdatePosition(bytes32 indexed id_, address indexed user, uint256 creditDecrease, uint256 pendingFeeDecrease, uint256 accruedFee); event MarketCreated(Market market, bytes32 indexed id_); - event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, address indexed maker, bool offerIsBuy, bytes32 group, uint256 tick, uint256 start, uint256 expiry, bool reduceOnly, uint256 maxUnits, uint256 maxAssets, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address receiverIfMakerIsSeller, address receiverIfTakerIsSeller, address payer, address buyerCallback, address sellerCallback); + event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, address indexed maker, bool offerIsBuy, bytes32 group, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer); event Withdraw(address caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address indexed receiver, uint256 pendingFeeDecrease); - event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer, address callback); + event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer); event SupplyCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf); event WithdrawCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf, address receiver); - event Liquidate(address caller, bytes32 indexed id_, address indexed collateral, uint256 seizedAssets, uint256 repaidUnits, address indexed borrower, bool postMaturityMode, address receiver, address payer, address callback, uint256 badDebt, uint256 latestLossFactor, uint256 latestContinuousFeeCredit); + event Liquidate(address caller, bytes32 indexed id_, address indexed collateral, uint256 seizedAssets, uint256 repaidUnits, address indexed borrower, bool postMaturityMode, address receiver, address payer, uint256 badDebt, uint256 latestLossFactor, uint256 latestContinuousFeeCredit); event SetConsumed(address indexed caller, bytes32 indexed group, uint256 amount, address indexed onBehalf); event FlashLoan(address indexed caller, address[] tokens, uint256[] assets, address indexed callback); event SetIsAuthorized(address indexed caller, address indexed authorized, bool newIsAuthorized, address indexed onBehalf); diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 2ca94a1fc..4536038cd 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -283,12 +283,6 @@ contract ContinuousFeeTest is BaseTest { otherLender, true, keccak256("lender-exit"), - MAX_TICK, - 0, - vm.getBlockTimestamp(), - false, - exitAmount, - 0, takeAssets, takeAssets, exitAmount, @@ -297,11 +291,7 @@ contract ContinuousFeeTest is BaseTest { exitAmount, exitAmount, lender, - address(0), - lender, - otherLender, - address(0), - address(0) + otherLender ); take(exitAmount, lender, _makeBuyOffer(exitAmount, keccak256("lender-exit"))); // lender is taker = seller diff --git a/test/LiquidationTest.sol b/test/LiquidationTest.sol index 8cc037a84..e2800d706 100644 --- a/test/LiquidationTest.sol +++ b/test/LiquidationTest.sol @@ -395,7 +395,6 @@ contract LiquidationTest is BaseTest { false, address(this), address(this), - address(0), expectedBadDebt, expectedLossFactor, expectedContinuousFeeCredit From 6d5d8a2cd27a8cb4046a0c25723d76a95cb235fa Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Wed, 3 Jun 2026 15:05:44 +0200 Subject: [PATCH 05/18] log more --- src/Midnight.sol | 19 +++++++++++++++---- src/libraries/EventsLib.sol | 3 ++- test/ContinuousFeeTest.sol | 19 +++++++++++++++---- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/Midnight.sol b/src/Midnight.sol index 32c8c71f7..5bb59f6e1 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -427,9 +427,6 @@ contract Midnight is IMidnight { id, units, taker, - offer.maker, - offer.buy, - offer.group, buyerAssets, sellerAssets, newConsumed, @@ -438,7 +435,21 @@ contract Midnight is IMidnight { buyerCreditIncrease, sellerCreditDecrease, receiver, - payer + payer, + takerCallback + ); + + emit EventsLib.TakenOffer( + offer.maker, + offer.tick, + offer.start, + offer.expiry, + offer.reduceOnly, + offer.maxUnits, + offer.maxAssets, + offer.buy, + offer.group, + offer.callback ); bool wasLocked = UtilsLib.tExchange(LIQUIDATION_LOCK_SLOT, id, seller, true); diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index ecdbc794e..19172b67e 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -19,7 +19,8 @@ library EventsLib { event SetDefaultContinuousFee(address indexed loanToken, uint256 newContinuousFee); event UpdatePosition(bytes32 indexed id_, address indexed user, uint256 creditDecrease, uint256 pendingFeeDecrease, uint256 accruedFee); event MarketCreated(Market market, bytes32 indexed id_); - event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, address indexed maker, bool offerIsBuy, bytes32 group, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer); + event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback); + event TakenOffer(address indexed maker, uint256 tick, uint256 start, uint256 expiry, bool reduceOnly, uint256 maxUnits, uint256 maxAssets, bool buy, bytes32 group, address callback); event Withdraw(address caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address indexed receiver, uint256 pendingFeeDecrease); event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer); event SupplyCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf); diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 4536038cd..5abc48393 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -280,9 +280,6 @@ contract ContinuousFeeTest is BaseTest { id, exitAmount, lender, - otherLender, - true, - keccak256("lender-exit"), takeAssets, takeAssets, exitAmount, @@ -291,7 +288,21 @@ contract ContinuousFeeTest is BaseTest { exitAmount, exitAmount, lender, - otherLender + otherLender, + address(0) + ); + vm.expectEmit(); + emit EventsLib.TakenOffer( + otherLender, + MAX_TICK, + 0, + vm.getBlockTimestamp(), + false, + exitAmount, + 0, + true, + keccak256("lender-exit"), + address(0) ); take(exitAmount, lender, _makeBuyOffer(exitAmount, keccak256("lender-exit"))); // lender is taker = seller From 6a02f63e7204f6b274e7bce24a77ac9cb1214847 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Tue, 16 Jun 2026 16:23:42 +0200 Subject: [PATCH 06/18] add missing ratifier --- src/Midnight.sol | 3 ++- src/libraries/EventsLib.sol | 2 +- test/ContinuousFeeTest.sol | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Midnight.sol b/src/Midnight.sol index 5bb59f6e1..6175f0859 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -449,7 +449,8 @@ contract Midnight is IMidnight { offer.maxAssets, offer.buy, offer.group, - offer.callback + offer.callback, + offer.ratifier ); bool wasLocked = UtilsLib.tExchange(LIQUIDATION_LOCK_SLOT, id, seller, true); diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 19172b67e..6d7b6031f 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -20,7 +20,7 @@ library EventsLib { event UpdatePosition(bytes32 indexed id_, address indexed user, uint256 creditDecrease, uint256 pendingFeeDecrease, uint256 accruedFee); event MarketCreated(Market market, bytes32 indexed id_); event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback); - event TakenOffer(address indexed maker, uint256 tick, uint256 start, uint256 expiry, bool reduceOnly, uint256 maxUnits, uint256 maxAssets, bool buy, bytes32 group, address callback); + event TakenOffer(address indexed maker, uint256 tick, uint256 start, uint256 expiry, bool reduceOnly, uint256 maxUnits, uint256 maxAssets, bool buy, bytes32 group, address callback, address ratifier); event Withdraw(address caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address indexed receiver, uint256 pendingFeeDecrease); event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer); event SupplyCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf); diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 5abc48393..02cd4b66a 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -302,7 +302,8 @@ contract ContinuousFeeTest is BaseTest { 0, true, keccak256("lender-exit"), - address(0) + address(0), + address(dummyRatifier) ); take(exitAmount, lender, _makeBuyOffer(exitAmount, keccak256("lender-exit"))); // lender is taker = seller From 4031172e6f03ac3da2aa4f3f2755dd3045c6a107 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Tue, 16 Jun 2026 18:11:03 +0200 Subject: [PATCH 07/18] fix event tests --- test/SetIsAuthorizedWithSigTest.sol | 4 ++-- test/TakeTest.sol | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/test/SetIsAuthorizedWithSigTest.sol b/test/SetIsAuthorizedWithSigTest.sol index 0634f12cc..33adb84d1 100644 --- a/test/SetIsAuthorizedWithSigTest.sol +++ b/test/SetIsAuthorizedWithSigTest.sol @@ -67,7 +67,7 @@ contract EcrecoverAuthorizerTest is BaseTest { Signature memory sig = signAuthorization(auth, borrower); vm.expectEmit(); - emit IEcrecoverAuthorizer.SetIsAuthorized(address(this), borrower, lender, true, auth.nonce); + emit IEcrecoverAuthorizer.SetIsAuthorized(address(this), borrower, lender, true, auth.nonce, borrower); ecrecoverAuthorizer.setIsAuthorized(auth, sig); @@ -78,7 +78,7 @@ contract EcrecoverAuthorizerTest is BaseTest { sig = signAuthorization(auth, borrower); vm.expectEmit(); - emit IEcrecoverAuthorizer.SetIsAuthorized(address(this), borrower, lender, false, auth.nonce); + emit IEcrecoverAuthorizer.SetIsAuthorized(address(this), borrower, lender, false, auth.nonce, borrower); ecrecoverAuthorizer.setIsAuthorized(auth, sig); diff --git a/test/TakeTest.sol b/test/TakeTest.sol index 86ac05415..5c189ba75 100644 --- a/test/TakeTest.sol +++ b/test/TakeTest.sol @@ -183,9 +183,6 @@ contract TakeTest is BaseTest { id, units, taker, - maker, - offerIsBuy, - group, buyerAssets, sellerAssets, existingConsumed + units, @@ -194,7 +191,22 @@ contract TakeTest is BaseTest { units - existingDebt, existingCredit, receiver, - address(payerCallback) + address(payerCallback), + offerIsBuy ? address(0) : address(payerCallback) + ); + vm.expectEmit(); + emit EventsLib.TakenOffer( + offer.maker, + offer.tick, + offer.start, + offer.expiry, + offer.reduceOnly, + offer.maxUnits, + offer.maxAssets, + offer.buy, + offer.group, + offer.callback, + offer.ratifier ); vm.prank(caller); From d1fa782369669c57a19934898a9e2ce938eba0b5 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Mon, 22 Jun 2026 09:39:38 +0200 Subject: [PATCH 08/18] fix tests --- test/ContinuousFeeTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 7204302f0..ce78d2fb1 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -298,7 +298,7 @@ contract ContinuousFeeTest is BaseTest { 0, vm.getBlockTimestamp(), false, - exitAmount, + type(uint256).max, 0, true, keccak256("lender-exit"), From e7c70aa37af4b288bf5ae4fe62d95585079076e8 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Wed, 3 Jun 2026 15:05:44 +0200 Subject: [PATCH 09/18] log more --- src/Midnight.sol | 19 +++++++++++++++---- src/libraries/EventsLib.sol | 3 ++- test/ContinuousFeeTest.sol | 19 +++++++++++++++---- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/Midnight.sol b/src/Midnight.sol index 7f5ea675e..26fa7cbca 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -436,9 +436,6 @@ contract Midnight is IMidnight { id, units, taker, - offer.maker, - offer.buy, - offer.group, buyerAssets, sellerAssets, newConsumed, @@ -447,7 +444,21 @@ contract Midnight is IMidnight { buyerCreditIncrease, sellerCreditDecrease, receiver, - payer + payer, + takerCallback + ); + + emit EventsLib.TakenOffer( + offer.maker, + offer.tick, + offer.start, + offer.expiry, + offer.reduceOnly, + offer.maxUnits, + offer.maxAssets, + offer.buy, + offer.group, + offer.callback ); bool wasLocked = UtilsLib.tExchange(LIQUIDATION_LOCK_SLOT, id, seller, true); diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index ecdbc794e..19172b67e 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -19,7 +19,8 @@ library EventsLib { event SetDefaultContinuousFee(address indexed loanToken, uint256 newContinuousFee); event UpdatePosition(bytes32 indexed id_, address indexed user, uint256 creditDecrease, uint256 pendingFeeDecrease, uint256 accruedFee); event MarketCreated(Market market, bytes32 indexed id_); - event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, address indexed maker, bool offerIsBuy, bytes32 group, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer); + event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback); + event TakenOffer(address indexed maker, uint256 tick, uint256 start, uint256 expiry, bool reduceOnly, uint256 maxUnits, uint256 maxAssets, bool buy, bytes32 group, address callback); event Withdraw(address caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address indexed receiver, uint256 pendingFeeDecrease); event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer); event SupplyCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf); diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 9b80ea47f..ad339eb53 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -280,9 +280,6 @@ contract ContinuousFeeTest is BaseTest { id, exitAmount, lender, - otherLender, - true, - keccak256("lender-exit"), takeAssets, takeAssets, exitAmount, @@ -291,7 +288,21 @@ contract ContinuousFeeTest is BaseTest { exitAmount, exitAmount, lender, - otherLender + otherLender, + address(0) + ); + vm.expectEmit(); + emit EventsLib.TakenOffer( + otherLender, + MAX_TICK, + 0, + vm.getBlockTimestamp(), + false, + exitAmount, + 0, + true, + keccak256("lender-exit"), + address(0) ); take(exitAmount, lender, _makeBuyOffer(keccak256("lender-exit"))); // lender is taker = seller From fb79c319d5dd871d863b1e08963fc8367f8eda3e Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Mon, 22 Jun 2026 11:19:18 +0200 Subject: [PATCH 10/18] evert "Merge remote-tracking branch 'origin/main' into hermes/ecrecover-authorizer-signer-event-1780397877" This reverts commit a0ea6944555cdb5171304948dcc7caaecb7a52e7, reversing changes made to e7c70aa37af4b288bf5ae4fe62d95585079076e8. --- certora/confs/PostMaturityDebt.conf | 10 +- certora/confs/ReentrancyView.conf | 1 - certora/specs/TickToPrice.spec | 4 +- src/Midnight.sol | 20 +- src/interfaces/IMidnight.sol | 22 +- src/libraries/TickLib.sol | 6 +- src/ratifiers/libraries/HashLib.sol | 47 +- test/BaseTest.sol | 3 - test/ContinuousFeeTest.sol | 111 ---- test/FrontendSignatureTest.sol | 6 +- test/HashLibTest.sol | 2 +- test/LiquidationTest.sol | 18 - test/OtherFunctionsTest.sol | 39 -- test/SettersTest.sol | 8 +- test/TakeAmountsTest.sol | 8 +- test/TakeTest.sol | 56 +- test/TickLibTest.sol | 15 +- test/frontend/sign-root.ts | 2 - test/ticks_exact.json | 926 +--------------------------- test/ticks_exact_gen.py | 2 +- 20 files changed, 71 insertions(+), 1235 deletions(-) diff --git a/certora/confs/PostMaturityDebt.conf b/certora/confs/PostMaturityDebt.conf index 231f1040a..bf82544a8 100644 --- a/certora/confs/PostMaturityDebt.conf +++ b/certora/confs/PostMaturityDebt.conf @@ -12,9 +12,13 @@ "optimistic_hashing": true, "hashing_length_bound": 2048, "prover_args": [ - "-destructiveOptimizations twostage", - "-s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" + "-mediumTimeout 60", + "-timeout 3600", + "-oldSplitParallel true", + "-dontStopAtFirstSplitTimeout true", + "-splitParallelTimelimit 7000", + "-splitParallelInitialDepth 3", + "-numOfParallelSplits 7" ], - "smt_timeout": 7200, "msg": "Midnight Post-Maturity Debt" } diff --git a/certora/confs/ReentrancyView.conf b/certora/confs/ReentrancyView.conf index 32dd95279..c7c44696a 100644 --- a/certora/confs/ReentrancyView.conf +++ b/certora/confs/ReentrancyView.conf @@ -14,6 +14,5 @@ "loop_iter": 5, "optimistic_hashing": true, "hashing_length_bound": 2048, - "smt_timeout": 7200, "msg": "Midnight Reentrancy View" } diff --git a/certora/specs/TickToPrice.spec b/certora/specs/TickToPrice.spec index 05ae46f02..619f94712 100644 --- a/certora/specs/TickToPrice.spec +++ b/certora/specs/TickToPrice.spec @@ -30,7 +30,7 @@ rule wExpCasting(uint256 x) { definition maxInput() returns int256 = assert_int256(lnOnePlusDelta() * (maxTick() / 2)); -definition maxOutput() returns uint256 = 20134595243400876315901952; +definition maxOutput() returns uint256 = 2010201770916298901946368; rule maxOutputIsWExpOfMaxInput() { assert maxOutput() == wExp(maxInput()); @@ -104,7 +104,7 @@ rule wExpIsMonotonicOnPositiveRangeWhenQJumps(int256 x) { /// tickToPrice properties /// // Useful for PriceToTick.spec -definition cvlMaxTick() returns uint256 = 6744; +definition cvlMaxTick() returns uint256 = 5820; rule cvlMaxTickIsMaxTick() { assert cvlMaxTick() == maxTick(); diff --git a/src/Midnight.sol b/src/Midnight.sol index d16b6b970..26fa7cbca 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -51,11 +51,8 @@ import {IMidnight, Market, Offer, CollateralParams, MarketState, Position} from /// CONTINUOUS FEES /// @dev A default continuous fee (per loan token) is set on new markets. Then, the fee setter can override it. /// @dev The fee is tracked per lender via pendingFee in each position. If the market's continuous fee changes, the -/// pending fee of existing lenders is not updated (=> their fee is fixed). If the market's continuious fee is decreased -/// lenders might self-take to exit and re-enter to reduce their pending fee (at the cost of the settlement fee). +/// pending fee of existing lenders is not updated (=> their fee is fixed). /// @dev In the absence of bad debt realizations, the face value of a lender's position is credit - pendingFee. -/// @dev An offer cannot be taken if its continuousFeeCap value is lower than the current market continuous fee. -/// This ensures maker buyers can protect against future continuous fee increases. /// /// LIQUIDATIONS /// @dev Accounts are liquidatable only if they are either unhealthy or the maturity has passed. The liquidation @@ -76,8 +73,6 @@ import {IMidnight, Market, Offer, CollateralParams, MarketState, Position} from /// minNewCollateral * liquidatedCollatPrice / LIF < rcfThreshold /// <=> (collateral - maxRepaid * LIF / liquidatedCollatPrice) * liquidatedCollatPrice / LIF < rcfThreshold /// <=> collateral * liquidatedCollatPrice / LIF - maxRepaid < rcfThreshold -/// @dev Nothing prevents borrowers to open small positions / liquidators to leave small positions that might not be -/// profitable to liquidate because of gas cost. The RCF deactivation at rcfThreshold just prevents the systemic aspect. /// @dev In the "post-maturity mode", the LIF (liquidation incentive factor) grows linearly from 1 at maturity to maxLif /// at maturity + TIME_TO_MAX_LIF, and the RCF is deactivated. /// @dev In both modes, maxLif is used to determine if the account has some bad debt, to always assume the worst case. @@ -91,7 +86,7 @@ import {IMidnight, Market, Offer, CollateralParams, MarketState, Position} from /// @dev To work as expected, all offers in the same group should have the same direction (offer.buy), max values and /// loan token. /// -/// OFFER SIZE +/// OFFER CAPS /// @dev Exactly one of maxAssets or maxUnits must be nonzero per offer (take reverts otherwise). /// @dev maxAssets caps max buyer assets if offer.buy is true, and caps max seller assets otherwise. /// @dev If maxAssets > 0, assets are capped to maxAssets, otherwise units are capped to maxUnits. @@ -268,7 +263,7 @@ contract Midnight is IMidnight { MarketState storage _marketState = marketState[id]; require(msg.sender == feeSetter, OnlyFeeSetter()); require(index <= 6, InvalidFeeIndex()); - require(newSettlementFee <= maxSettlementFee(index), SettlementFeeAboveMax()); + require(newSettlementFee <= maxSettlementFee(index), SettlementFeeTooHigh()); require(newSettlementFee % CBP == 0, FeeNotMultipleOfFeeCbp()); require(_marketState.tickSpacing > 0, MarketNotCreated()); // forge-lint: disable-next-item(unsafe-typecast) as newSettlementFee <= maxSettlementFee <= uint16.max * CBP @@ -286,7 +281,7 @@ contract Midnight is IMidnight { function setDefaultSettlementFee(address loanToken, uint256 index, uint256 newSettlementFee) external { require(msg.sender == feeSetter, OnlyFeeSetter()); require(index <= 6, InvalidFeeIndex()); - require(newSettlementFee <= maxSettlementFee(index), SettlementFeeAboveMax()); + require(newSettlementFee <= maxSettlementFee(index), SettlementFeeTooHigh()); require(newSettlementFee % CBP == 0, FeeNotMultipleOfFeeCbp()); // forge-lint: disable-next-item(unsafe-typecast) as newSettlementFee <= maxSettlementFee <= uint16.max * CBP defaultSettlementFeeCbp[loanToken][index] = uint16(newSettlementFee / CBP); @@ -296,7 +291,7 @@ contract Midnight is IMidnight { function setMarketContinuousFee(bytes32 id, uint256 newContinuousFee) external { MarketState storage _marketState = marketState[id]; require(msg.sender == feeSetter, OnlyFeeSetter()); - require(newContinuousFee <= MAX_CONTINUOUS_FEE, ContinuousFeeAboveMax()); + require(newContinuousFee <= MAX_CONTINUOUS_FEE, ContinuousFeeTooHigh()); require(_marketState.tickSpacing > 0, MarketNotCreated()); // forge-lint: disable-next-line(unsafe-typecast) as newContinuousFee <= MAX_CONTINUOUS_FEE < type(uint32).max _marketState.continuousFee = uint32(newContinuousFee); @@ -305,7 +300,7 @@ contract Midnight is IMidnight { function setDefaultContinuousFee(address loanToken, uint256 newContinuousFee) external { require(msg.sender == feeSetter, OnlyFeeSetter()); - require(newContinuousFee <= MAX_CONTINUOUS_FEE, ContinuousFeeAboveMax()); + require(newContinuousFee <= MAX_CONTINUOUS_FEE, ContinuousFeeTooHigh()); // forge-lint: disable-next-line(unsafe-typecast) as newContinuousFee <= MAX_CONTINUOUS_FEE < type(uint32).max defaultContinuousFee[loanToken] = uint32(newContinuousFee); emit EventsLib.SetDefaultContinuousFee(loanToken, newContinuousFee); @@ -357,7 +352,6 @@ contract Midnight is IMidnight { MarketState storage _marketState = marketState[id]; require(_marketState.lossFactor < type(uint128).max, MarketLossFactorMaxedOut()); require((offer.maxAssets == 0) != (offer.maxUnits == 0), InvalidOfferCaps()); - require(_marketState.continuousFee <= offer.continuousFeeCap, ContinuousFeeAboveOfferCap()); require(offer.tick % _marketState.tickSpacing == 0, TickNotAccessible()); require(block.timestamp >= offer.start, OfferNotStarted()); require(block.timestamp <= offer.expiry, OfferExpired()); @@ -367,7 +361,7 @@ contract Midnight is IMidnight { UnusedReceiverMustBeZero() ); require(isAuthorized[offer.maker][offer.ratifier], RatifierUnauthorized()); - require(IRatifier(offer.ratifier).isRatified(offer, ratifierData) == CALLBACK_SUCCESS, RatifierFailed()); + require(IRatifier(offer.ratifier).isRatified(offer, ratifierData) == CALLBACK_SUCCESS, RatifierFail()); uint256 offerPrice = TickLib.tickToPrice(offer.tick); uint256 timeToMaturity = UtilsLib.zeroFloorSub(offer.market.maturity, block.timestamp); diff --git a/src/interfaces/IMidnight.sol b/src/interfaces/IMidnight.sol index 5f27fe0bc..eb5af676c 100644 --- a/src/interfaces/IMidnight.sol +++ b/src/interfaces/IMidnight.sol @@ -33,7 +33,6 @@ struct Offer { bool reduceOnly; uint256 maxUnits; uint256 maxAssets; // buyerAssets if offer.buy else sellerAssets - uint256 continuousFeeCap; } /// @dev Settlement fee cbp values and the continuous fee are 0 until the market is created, then set to the default @@ -72,10 +71,14 @@ interface IMidnight { error CollateralParamsNotSorted(); error ConsumedAssets(); error ConsumedUnits(); - error ContinuousFeeAboveMax(); - error ContinuousFeeAboveOfferCap(); + error ContinuousFeeTooHigh(); error FeeNotMultipleOfFeeCbp(); error InconsistentInput(); + error WrongBuyCallbackReturnValue(); + error WrongSellCallbackReturnValue(); + error WrongRepayCallbackReturnValue(); + error WrongLiquidateCallbackReturnValue(); + error WrongFlashLoanCallbackReturnValue(); error InvalidFeeIndex(); error InvalidMaxLif(); error InvalidOfferCaps(); @@ -83,37 +86,32 @@ interface IMidnight { error LiquidatorGatedFromLiquidating(); error LltvNotAllowed(); error MakerCreditOrDebtIncreased(); - error MarketLossFactorMaxedOut(); - error MarketNotCreated(); error MaturityTooFar(); error NoCollateralParams(); error NotBorrower(); error NotLiquidatable(); + error MarketLossFactorMaxedOut(); + error MarketNotCreated(); error OfferExpired(); error OfferNotStarted(); error OnlyFeeClaimer(); error OnlyFeeSetter(); error OnlyRoleSetter(); error OnlyTickSpacingSetter(); - error RatifierFailed(); + error RatifierFail(); error RatifierUnauthorized(); error RecoveryCloseFactorConditionsViolated(); error SelfTake(); error SellerGatedFromIncreasingDebt(); error SellerIsLiquidatable(); - error SettlementFeeAboveMax(); error TakerUnauthorized(); error TickNotAccessible(); error TooManyActivatedCollaterals(); error TooManyCollateralParams(); + error SettlementFeeTooHigh(); error Unauthorized(); error UnhealthyBorrower(); error UnusedReceiverMustBeZero(); - error WrongBuyCallbackReturnValue(); - error WrongFlashLoanCallbackReturnValue(); - error WrongLiquidateCallbackReturnValue(); - error WrongRepayCallbackReturnValue(); - error WrongSellCallbackReturnValue(); // forgefmt: disable-start /// IMMUTABLES /// diff --git a/src/libraries/TickLib.sol b/src/libraries/TickLib.sol index 9f72b4089..e71872b6c 100644 --- a/src/libraries/TickLib.sol +++ b/src/libraries/TickLib.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.0; int256 constant LN_ONE_PLUS_DELTA = 0.004987541511039073e18; // floor(ln(1.005) * 1e18) -uint256 constant MAX_TICK = 6744; -// Minimum representable price increment in WAD (1e-7 WAD). Tick prices are rounded to multiples of this value. -uint256 constant PRICE_ROUNDING_STEP = 1e11; +uint256 constant MAX_TICK = 5820; +// Minimum representable price increment in WAD (1e-6 WAD). Tick prices are rounded to multiples of this value. +uint256 constant PRICE_ROUNDING_STEP = 1e12; library TickLib { using TickLib for uint256; diff --git a/src/ratifiers/libraries/HashLib.sol b/src/ratifiers/libraries/HashLib.sol index 13d8be011..a377b16ea 100644 --- a/src/ratifiers/libraries/HashLib.sol +++ b/src/ratifiers/libraries/HashLib.sol @@ -9,7 +9,7 @@ bytes32 constant COLLATERAL_PARAMS_TYPEHASH = 0xaf44a88eb50ebdbbebd980e5a23045c4 /// @dev keccak256(bytes.concat(MARKET_TYPE, COLLATERAL_PARAMS_TYPE)). bytes32 constant MARKET_TYPEHASH = 0x358117e98511cc3df97175dca58053b06675b43ad090b0553f8a1eff008b6e2e; /// @dev keccak256(bytes.concat(OFFER_TYPE, COLLATERAL_PARAMS_TYPE, MARKET_TYPE)). -bytes32 constant OFFER_TYPEHASH = 0x6bd2a06ec6952feb97c3e3b4f7de6c342f12b1ac769d5c91368271af636c85b7; +bytes32 constant OFFER_TYPEHASH = 0x980a4cfc9766df84667f316d76e10cefc8caf04fb4cd4a9fca00a8e7b34f619c; library HashLib { error LeafIndexOutOfRange(); @@ -21,28 +21,28 @@ library HashLib { /// @dev Reverts if height is greater than 20. function offerTreeTypeHash(uint256 height) internal pure returns (bytes32) { if (height <= 10) { - if (height == 0) return 0xc27c38e446b48c820ab9c4373dc63a4a750a08165cb4bb488206ebabe045d650; - if (height == 1) return 0x4e15d8736f4406e07bf9844b1653474472a827130c61e899bf1f574a88b8d987; - if (height == 2) return 0x46d107447b480c38ef5b7f54603dba0cb23b887f302b01a998b9d8a80320dd53; - if (height == 3) return 0xd1f3607a8e81454bb3baf5f898274ab47541fffc690278a74f13e174e116be72; - if (height == 4) return 0xb2d98adca9d116c9bc02ce59ec599ac3c2d33db1c0d1217c7e411d9198d427be; - if (height == 5) return 0x5931e0597fcf986027f3118b2495a9ac22139d133f9ad2c2198e6738dc3886c5; - if (height == 6) return 0x3967d37928614a085b47e8758fbc3869a8aed63bdf60ccee8536ff2b5064da06; - if (height == 7) return 0xd6b9f5f45915a260f6e521d9b40f86c385730b6bc330590fcde212e2fea64263; - if (height == 8) return 0x080caa519dbd5328c119d9907e0fa3d9a50dc2ae4bf6dd42c93c100dbc89b51a; - if (height == 9) return 0x45da471048924165ea2ad1855ba940e454b486e71dcf1666c71a928c8844c419; - return 0xa49a9434fc1836bd08097368325b31039b6a0fd44919f53e4d8f4bf814084cb0; + if (height == 0) return 0x2b9ee710e1977dfc5778fe18c905ccc1d9e144baf3ba83be732d4da65ecb73e3; + if (height == 1) return 0x3cc16189b92a85898f1d5c6e87282c8ded7c1c93b2323d5e85ae10c5f4b2b220; + if (height == 2) return 0x6de37d3e570afa293a8107d4b6b1d9547616c04f42164d009c89194787b2ffa6; + if (height == 3) return 0xba3ea2ddfbf40a906fcd1b9506dbd344c062e8dcba8b5c902ceb13339f45a358; + if (height == 4) return 0xe5faa865e93bc1b7b8fdf91980f54682d649683b014edd6c54b642f5a0c96977; + if (height == 5) return 0xeda50f61dd2a827c6ff9fbfcd54335628dcaa78aaa4f2d118c60886219cdce2b; + if (height == 6) return 0x54e2c9cc40cdc0e9ad530cf2be298f952f57af2b18b02f88274a9bbab359d23a; + if (height == 7) return 0xc9d81859d60d6b21c688f4be93ca83e3be222728bb156ef5f4cf497f879f1e29; + if (height == 8) return 0xd59b0c4544e0c60c8611eab0aaa402575f14ee784d22289c5d57f48c422a62d6; + if (height == 9) return 0xccad21701f34f08bb8398a3dbc77e20e4c9c424930f3a8b31485bf059e2bdb20; + return 0x8a42dfb49807647bfc49c906aef322aa0239d40e4cb675761e141bc7bfa530da; } else { - if (height == 11) return 0xd3e93e4525132f0187a6964dc01fef33fde414538ffd212e9f2f478c3263e0a0; - if (height == 12) return 0x25990db2d26547f92c711988300df317af57bad5cd5d9d8e787a82f95c929474; - if (height == 13) return 0x8e0c648afa977572ead40a1d10a6db2c425b8099545006d834a7b849c6166643; - if (height == 14) return 0x4b635250efa6243e277fdd0cf6df993c2943b64f10f3a0756ceb1f47ef8f9b18; - if (height == 15) return 0xbde1c927f6222c07c8df264e68b42b8382c7c2b85f4729e0df94297cfeebfa91; - if (height == 16) return 0x4d58aea1a67f94be21ab1415bf3b602592430eb9112268fd0fc4e141b1a35e76; - if (height == 17) return 0x14c03281bce13010b158e5a4a3378be394ac9e16118aedb17d82ced51e66836c; - if (height == 18) return 0x99fd3e76f43b2cc221cb9860bc6c96cda95af3fa07ef5f04e071b54aa9386d06; - if (height == 19) return 0x1b1c2f1a04968094d8d0453d49838f7a809d1202ae04a1c2e0964e442ff7988b; - if (height == 20) return 0xc8ccd3cb3267dd76f563584920ac60f2283b719917481b85fe5e10b754932455; + if (height == 11) return 0x2adc0d948b2e3ecb642661590d2eec36d4e71e9acf382deb6574371800caf198; + if (height == 12) return 0xf5845dfaed016de272342f346346a49d4b1694f622144d420558a38e46ac9dad; + if (height == 13) return 0x3d7df854e6294bf433b64bbb8d0a82fa875a87b45b0016db27fc5752e54126ad; + if (height == 14) return 0x72a991a101708716ff427c524404ab44f4d4d1f4e7e76c0ae8b967222164b348; + if (height == 15) return 0x762c88fc52cf78a54401d247790f1bdb619d51d3458d1415c20d1422197cecc4; + if (height == 16) return 0x8ede2209e94c8d5f8379d733dc8712b71a3888c1c4b70f3d6b22285f70bf4286; + if (height == 17) return 0x425b18f07b3ac2f641977d2c294590565dd40b5d8414610568dca64628399975; + if (height == 18) return 0x7e7d98718c0180e882e5963b9bd49810096912c273dfa38d8afdd6d39fde86ec; + if (height == 19) return 0x8d35d491a29d846489e19688efff3c4cc7dbd54458058d49b30294074539f0b9; + if (height == 20) return 0x824e385eea1953bcbc783bf900b18aa6fba129b6908765e986cf0968b491ec4f; revert TreeTooHigh(); } } @@ -131,8 +131,7 @@ library HashLib { offer.ratifier, offer.reduceOnly, offer.maxUnits, - offer.maxAssets, - offer.continuousFeeCap + offer.maxAssets ) ); } diff --git a/test/BaseTest.sol b/test/BaseTest.sol index ea494bdd9..f4e818f76 100644 --- a/test/BaseTest.sol +++ b/test/BaseTest.sol @@ -183,7 +183,6 @@ abstract contract BaseTest is Test { lenderOffer.buy = true; lenderOffer.maker = otherLender; lenderOffer.maxUnits = type(uint256).max; - lenderOffer.continuousFeeCap = type(uint256).max; lenderOffer.group = keccak256(abi.encode("non zero group")); lenderOffer.ratifier = address(dummyRatifier); lenderOffer.expiry = vm.getBlockTimestamp() + 200; @@ -209,7 +208,6 @@ abstract contract BaseTest is Test { badBorrowerOffer.start = vm.getBlockTimestamp(); badBorrowerOffer.expiry = vm.getBlockTimestamp() + 200; badBorrowerOffer.tick = MAX_TICK; - badBorrowerOffer.continuousFeeCap = type(uint256).max; vm.prank(badBorrower); @@ -326,7 +324,6 @@ abstract contract BaseTest is Test { borrowerOffer.start = vm.getBlockTimestamp(); borrowerOffer.expiry = vm.getBlockTimestamp(); borrowerOffer.tick = MAX_TICK; - borrowerOffer.continuousFeeCap = type(uint256).max; } function max(uint256 a, uint256 b) internal pure returns (uint256) { diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 4c159b326..ad339eb53 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -59,7 +59,6 @@ contract ContinuousFeeTest is BaseTest { o.buy = true; o.maker = otherLender; o.maxUnits = type(uint256).max; - o.continuousFeeCap = type(uint256).max; o.ratifier = address(dummyRatifier); o.expiry = vm.getBlockTimestamp(); o.tick = MAX_TICK; @@ -194,7 +193,6 @@ contract ContinuousFeeTest is BaseTest { borrowOffer.start = vm.getBlockTimestamp(); borrowOffer.expiry = vm.getBlockTimestamp(); borrowOffer.tick = MAX_TICK; - borrowOffer.continuousFeeCap = type(uint256).max; } function testTwoLendersDifferentRates( @@ -450,40 +448,6 @@ contract ContinuousFeeTest is BaseTest { assertEq(midnight.withdrawable(id), withdrawableBefore - claimAmount, "withdrawable after claim"); } - function testClaimContinuousFeeDecrementsWithdrawable(uint256 credit, uint256 feeRate, uint256 ttm, uint256 elapsed) - public - { - credit = bound(credit, 1, MAX_CREDIT); - feeRate = bound(feeRate, 1, MAX_CONTINUOUS_FEE); - ttm = bound(ttm, 2, 360 days); - elapsed = bound(elapsed, 1, ttm - 1); - - setupLender(credit, feeRate, ttm); - - vm.warp(vm.getBlockTimestamp() + elapsed); - midnight.updatePosition(market, lender); - - uint256 feeAmount = midnight.continuousFeeCredit(id); - vm.assume(feeAmount > 1); - uint256 claimAmount = feeAmount / 2; // two claims stay within the accrued fee. - - // Repay so withdrawable covers the claims. - deal(address(loanToken), borrower, credit); - vm.prank(borrower); - midnight.repay(market, credit, borrower, address(0), hex""); - - address receiver = makeAddr("receiver"); - uint256 withdrawableBefore = midnight.withdrawable(id); - - vm.prank(feeClaimer); - midnight.claimContinuousFee(market, claimAmount, receiver); - assertEq(midnight.withdrawable(id), withdrawableBefore - claimAmount, "withdrawable after first claim"); - - vm.prank(feeClaimer); - midnight.claimContinuousFee(market, claimAmount, receiver); - assertEq(midnight.withdrawable(id), withdrawableBefore - 2 * claimAmount, "withdrawable after second claim"); - } - function testClaimContinuousFeeOnlyFeeClaimer(address caller) public { vm.assume(caller != feeClaimer); vm.prank(caller); @@ -584,79 +548,4 @@ contract ContinuousFeeTest is BaseTest { setupLender(1e18, 0, 100 days); assertEq(midnight.lastAccrual(id, makeAddr("nobody")), 0, "lastAccrual zero for fresh position"); } - - /// @dev Creates the market with continuousFee == feeRate (touch copies the default fee). - function _setupFeeMarket(uint256 feeRate) internal { - market.maturity = vm.getBlockTimestamp() + 100 days; - id = toId(market); - midnight.setDefaultContinuousFee(address(loanToken), feeRate); - midnight.touchMarket(market); - assertEq(midnight.continuousFee(id), feeRate, "market continuousFee"); - } - - function testTakeRevertsWhenBuyOfferContinuousFeeCapExceeded(uint256 feeRate, uint256 continuousFeeCap) public { - feeRate = bound(feeRate, 1, MAX_CONTINUOUS_FEE); - continuousFeeCap = bound(continuousFeeCap, 0, feeRate - 1); - _setupFeeMarket(feeRate); - - uint256 units = 1e18; - collateralize(market, borrower, units * 2); - deal(address(loanToken), otherLender, units); - - Offer memory offer = _makeBuyOffer(keccak256("buy-max-fee")); - offer.maxUnits = units; - offer.continuousFeeCap = continuousFeeCap; - - vm.expectRevert(IMidnight.ContinuousFeeAboveOfferCap.selector); - take(units, borrower, offer); // borrower is the seller, otherLender (maker) is the buyer. - } - - function testTakeSucceedsWhenBuyOfferContinuousFeeCapRespected(uint256 feeRate, uint256 continuousFeeCap) public { - feeRate = bound(feeRate, 0, MAX_CONTINUOUS_FEE); - continuousFeeCap = bound(continuousFeeCap, feeRate, type(uint256).max); - _setupFeeMarket(feeRate); - - uint256 units = 1e18; - collateralize(market, borrower, units * 2); - deal(address(loanToken), otherLender, units); - - Offer memory offer = _makeBuyOffer(keccak256("buy-ok-fee")); - offer.maxUnits = units; - offer.continuousFeeCap = continuousFeeCap; - - take(units, borrower, offer); - assertEq(midnight.debtOf(id, borrower), units, "borrower took on debt"); - } - - function testTakeRevertsWhenSellOfferContinuousFeeCapExceeded(uint256 feeRate, uint256 continuousFeeCap) public { - feeRate = bound(feeRate, 1, MAX_CONTINUOUS_FEE); - continuousFeeCap = bound(continuousFeeCap, 0, feeRate - 1); - _setupFeeMarket(feeRate); - - uint256 units = 1e18; - collateralize(market, otherBorrower, units * 2); - deal(address(loanToken), lender, units); - - Offer memory offer = _makeBorrowOffer(units); - offer.continuousFeeCap = continuousFeeCap; - - vm.expectRevert(IMidnight.ContinuousFeeAboveOfferCap.selector); - take(units, lender, offer); // lender is the buyer, otherBorrower (maker) is the seller. - } - - function testTakeSucceedsWhenSellOfferContinuousFeeCapRespected(uint256 feeRate, uint256 continuousFeeCap) public { - feeRate = bound(feeRate, 0, MAX_CONTINUOUS_FEE); - continuousFeeCap = bound(continuousFeeCap, feeRate, type(uint256).max); - _setupFeeMarket(feeRate); - - uint256 units = 1e18; - collateralize(market, otherBorrower, units * 2); - deal(address(loanToken), lender, units); - - Offer memory offer = _makeBorrowOffer(units); - offer.continuousFeeCap = continuousFeeCap; - - take(units, lender, offer); // lender is the buyer, otherBorrower (maker) is the seller. - assertEq(midnight.creditOf(id, lender), units, "lender gained credit"); - } } diff --git a/test/FrontendSignatureTest.sol b/test/FrontendSignatureTest.sol index df675cbe5..0f0d1527f 100644 --- a/test/FrontendSignatureTest.sol +++ b/test/FrontendSignatureTest.sol @@ -9,10 +9,10 @@ import {CALLBACK_SUCCESS} from "../src/libraries/ConstantsLib.sol"; import {HashLib} from "../src/ratifiers/libraries/HashLib.sol"; // Paste from frontend output. -address constant ACCOUNT = 0xFDa6883171208B36122229505FB2D6F30c052311; +address constant ACCOUNT = 0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A; uint8 constant SIG_V = 28; -bytes32 constant SIG_R = 0xb7a8c34b3aa87d799f2bd6e01a36c6a48673313015c592fc4137043b37ee80c6; -bytes32 constant SIG_S = 0x5161ce684b17e81a0b297441856e8d8b498c541116fd6dde5d87e5624b847afb; +bytes32 constant SIG_R = 0x3b634e6e609860ff1d80ec02a97d6d82bfe7ff35a8108120138ff561460d7040; +bytes32 constant SIG_S = 0x3eb97018d5ccf0711062df8c70faea0971c4f8e9556d57673a03246728bd91c6; address constant RATIFIER = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB; diff --git a/test/HashLibTest.sol b/test/HashLibTest.sol index 42f5b5e9b..a72b62a51 100644 --- a/test/HashLibTest.sol +++ b/test/HashLibTest.sol @@ -14,7 +14,7 @@ bytes constant COLLATERAL_PARAMS_TYPE = "CollateralParams(address token,uint256 bytes constant MARKET_TYPE = "Market(address loanToken,CollateralParams[] collateralParams,uint256 maturity,uint256 rcfThreshold,address enterGate,address liquidatorGate)"; bytes constant OFFER_TYPE = - "Offer(Market market,bool buy,address maker,uint256 start,uint256 expiry,uint256 tick,bytes32 group,address callback,bytes callbackData,address receiverIfMakerIsSeller,address ratifier,bool reduceOnly,uint256 maxUnits,uint256 maxAssets,uint256 continuousFeeCap)"; + "Offer(Market market,bool buy,address maker,uint256 start,uint256 expiry,uint256 tick,bytes32 group,address callback,bytes callbackData,address receiverIfMakerIsSeller,address ratifier,bool reduceOnly,uint256 maxUnits,uint256 maxAssets)"; contract HashLibTest is Test { function testCollateralParamsTypeHash() public pure { diff --git a/test/LiquidationTest.sol b/test/LiquidationTest.sol index cb95826ff..e2800d706 100644 --- a/test/LiquidationTest.sol +++ b/test/LiquidationTest.sol @@ -579,24 +579,6 @@ contract LiquidationTest is BaseTest { assertLe(remainingDebt, newMaxDebt + 3, "position should be approximately just healthy after max repayment"); } - function testLiquidateAccumulatesWithdrawable(uint256 units, uint256 repaid) public { - units = bound(units, 100, MAX_UNITS); - collateralize(market, borrower, units); - setupMarket(market, units); - vm.warp(market.maturity + TIME_TO_MAX_LIF); // post-maturity: liquidatable with no recovery close factor cap. - Oracle(market.collateralParams[0].oracle).setPrice(ORACLE_PRICE_SCALE); - - repaid = bound(repaid, 1, units / 8); // two equal repays stay within debt and collateral capacity. - - assertEq(midnight.withdrawable(id), 0, "withdrawable before"); - - midnight.liquidate(market, 0, 0, repaid, borrower, true, address(this), address(0), ""); - assertEq(midnight.withdrawable(id), repaid, "withdrawable after first liquidation"); - - midnight.liquidate(market, 0, 0, repaid, borrower, true, address(this), address(0), ""); - assertEq(midnight.withdrawable(id), 2 * repaid, "withdrawable after second liquidation"); - } - /// @dev When rcfThreshold > remaining debt after max repayment, full liquidation is allowed pre-maturity. function testRcfThresholdAllowsFullLiquidation(uint256 units, uint256 liquidationOraclePrice, uint256 rcfThreshold) public diff --git a/test/OtherFunctionsTest.sol b/test/OtherFunctionsTest.sol index 03660e36a..3d2274a50 100644 --- a/test/OtherFunctionsTest.sol +++ b/test/OtherFunctionsTest.sol @@ -194,45 +194,6 @@ contract OtherFunctionsTest is BaseTest { assertEq(loanToken.balanceOf(receiver), withdraw, "balance of receiver"); } - function testRepayAccumulatesWithdrawable(uint256 units, uint256 repaid) public { - units = bound(units, 2, MAX_UNITS); - repaid = bound(repaid, 1, units / 2); // two equal repays stay within debt. - collateralize(market, borrower, units); - setupMarket(market, units); - deal(address(loanToken), borrower, 2 * repaid); - - assertEq(midnight.withdrawable(id), 0, "withdrawable before"); - - vm.prank(borrower); - midnight.repay(market, repaid, borrower, address(0), hex""); - assertEq(midnight.withdrawable(id), repaid, "withdrawable after first repay"); - - vm.prank(borrower); - midnight.repay(market, repaid, borrower, address(0), hex""); - assertEq(midnight.withdrawable(id), 2 * repaid, "withdrawable after second repay"); - } - - function testWithdrawDecrementsWithdrawable(uint256 units, uint256 withdrawn) public { - units = bound(units, 2, MAX_UNITS); - withdrawn = bound(withdrawn, 1, units / 2); // two equal withdraws stay within credit. - collateralize(market, borrower, units); - setupMarket(market, units); - - // Fully repay so `units` is withdrawable. - deal(address(loanToken), borrower, units); - vm.prank(borrower); - midnight.repay(market, units, borrower, address(0), hex""); - assertEq(midnight.withdrawable(id), units, "withdrawable before"); - - vm.prank(lender); - midnight.withdraw(market, withdrawn, lender, lender); - assertEq(midnight.withdrawable(id), units - withdrawn, "withdrawable after first withdraw"); - - vm.prank(lender); - midnight.withdraw(market, withdrawn, lender, lender); - assertEq(midnight.withdrawable(id), units - 2 * withdrawn, "withdrawable after second withdraw"); - } - function testWithdrawCollateralToReceiver(uint256 supply, uint256 withdraw) public { supply = bound(supply, 1, MAX_UNITS); withdraw = bound(withdraw, 1, supply); diff --git a/test/SettersTest.sol b/test/SettersTest.sol index f6e59c6d2..29910f956 100644 --- a/test/SettersTest.sol +++ b/test/SettersTest.sol @@ -162,7 +162,7 @@ contract SettersTest is BaseTest { function testSetMarketSettlementFeeValueTooHigh(bytes32 id, uint256 feeTooHigh, uint256 index) public { index = bound(index, 0, 6); feeTooHigh = bound(feeTooHigh, maxSettlementFee(index) + 1, 1e18); - vm.expectRevert(IMidnight.SettlementFeeAboveMax.selector); + vm.expectRevert(IMidnight.SettlementFeeTooHigh.selector); midnight.setMarketSettlementFee(id, index, feeTooHigh); } @@ -305,7 +305,7 @@ contract SettersTest is BaseTest { function testSetDefaultSettlementFeeValidation(address loanToken, uint256 feeTooHigh, uint256 index) public { index = bound(index, 0, 6); feeTooHigh = bound(feeTooHigh, maxSettlementFee(index) + 1, 1e18); - vm.expectRevert(IMidnight.SettlementFeeAboveMax.selector); + vm.expectRevert(IMidnight.SettlementFeeTooHigh.selector); midnight.setDefaultSettlementFee(loanToken, index, feeTooHigh); } @@ -422,10 +422,10 @@ contract SettersTest is BaseTest { midnight.touchMarket(market); bytes32 id = toId(market); - vm.expectRevert(IMidnight.ContinuousFeeAboveMax.selector); + vm.expectRevert(IMidnight.ContinuousFeeTooHigh.selector); midnight.setMarketContinuousFee(id, fee); - vm.expectRevert(IMidnight.ContinuousFeeAboveMax.selector); + vm.expectRevert(IMidnight.ContinuousFeeTooHigh.selector); midnight.setDefaultContinuousFee(address(loanToken), fee); } diff --git a/test/TakeAmountsTest.sol b/test/TakeAmountsTest.sol index d69a8b1b1..22886ee40 100644 --- a/test/TakeAmountsTest.sol +++ b/test/TakeAmountsTest.sol @@ -12,8 +12,6 @@ import {TakeAmountsLib} from "../src/periphery/TakeAmountsLib.sol"; contract TakeAmountsTest is BaseTest { using UtilsLib for uint256; - uint256 internal constant BORROWER_POSITION_UNITS = 1e37; - Market internal market; bytes32 internal id; Offer internal offer; @@ -146,7 +144,7 @@ contract TakeAmountsTest is BaseTest { targetBuyerAssets = bound(targetBuyerAssets, 1, 1e30); tick = bound(tick, 4, _maxTick(settlementFee) / DEFAULT_TICK_SPACING) * DEFAULT_TICK_SPACING; - _createPosition(BORROWER_POSITION_UNITS); + _createPosition(1e36); offer.maker = lender; offer.receiverIfMakerIsSeller = lender; @@ -169,7 +167,7 @@ contract TakeAmountsTest is BaseTest { targetSellerAssets = bound(targetSellerAssets, 1, 1e30); tick = bound(tick, 4, _maxTick(settlementFee) / DEFAULT_TICK_SPACING) * DEFAULT_TICK_SPACING; - _createPosition(BORROWER_POSITION_UNITS); + _createPosition(1e36); offer.maker = lender; offer.receiverIfMakerIsSeller = lender; @@ -214,7 +212,7 @@ contract TakeAmountsTest is BaseTest { uint256 settlementFee = _setSettlementFees(settlementFee0, settlementFee1); targetBuyerAssets = bound(targetBuyerAssets, 1, 1e30); - _createPosition(BORROWER_POSITION_UNITS); + _createPosition(1e36); uint256 buyerPrice = TickLib.tickToPrice(MAX_TICK) + settlementFee; uint256 targetUnits = targetBuyerAssets.mulDivUp(WAD, buyerPrice); diff --git a/test/TakeTest.sol b/test/TakeTest.sol index e8c820b0b..86ac05415 100644 --- a/test/TakeTest.sol +++ b/test/TakeTest.sol @@ -64,7 +64,6 @@ contract TakeTest is BaseTest { lenderOffer.maker = lender; lenderOffer.ratifier = address(dummyRatifier); lenderOffer.maxUnits = type(uint256).max; - lenderOffer.continuousFeeCap = type(uint256).max; lenderOffer.market = market; lenderOffer.expiry = vm.getBlockTimestamp() + 200; lenderOffer.tick = MAX_TICK; @@ -74,7 +73,6 @@ contract TakeTest is BaseTest { otherLenderOffer.ratifier = address(dummyRatifier); otherLenderOffer.receiverIfMakerIsSeller = otherLender; otherLenderOffer.maxUnits = type(uint256).max; - otherLenderOffer.continuousFeeCap = type(uint256).max; otherLenderOffer.market = market; otherLenderOffer.expiry = vm.getBlockTimestamp() + 200; otherLenderOffer.tick = MAX_TICK; @@ -84,7 +82,6 @@ contract TakeTest is BaseTest { borrowerOffer.ratifier = address(dummyRatifier); borrowerOffer.receiverIfMakerIsSeller = borrower; borrowerOffer.maxUnits = type(uint256).max; - borrowerOffer.continuousFeeCap = type(uint256).max; borrowerOffer.market = market; borrowerOffer.expiry = vm.getBlockTimestamp() + 200; borrowerOffer.tick = MAX_TICK; @@ -93,7 +90,6 @@ contract TakeTest is BaseTest { otherBorrowerOffer.maker = otherBorrower; otherBorrowerOffer.ratifier = address(dummyRatifier); otherBorrowerOffer.maxUnits = type(uint256).max; - otherBorrowerOffer.continuousFeeCap = type(uint256).max; otherBorrowerOffer.market = market; otherBorrowerOffer.expiry = vm.getBlockTimestamp() + 200; otherBorrowerOffer.tick = MAX_TICK; @@ -129,7 +125,6 @@ contract TakeTest is BaseTest { collateralize(market, lender, existingDebt); Offer memory buy0 = _setupMarketOffer(market, existingDebt); buy0.buy = true; - buy0.continuousFeeCap = type(uint256).max; buy0.maker = otherLender; buy0.receiverIfMakerIsSeller = address(0); // forge-lint: disable-next-line(unsafe-typecast) @@ -942,55 +937,6 @@ contract TakeTest is BaseTest { assertEq(buyerAssets, expectedBuyerAssets); } - function testMaxAssetsSellerConsumedAccumulates(uint256 units) public { - units = bound(units, 1, maxAssets / 2); - deal(address(loanToken), lender, type(uint128).max); - collateralize(market, borrower, 2 * units); - - borrowerOffer.maxUnits = 0; - borrowerOffer.maxAssets = type(uint128).max; - - (, uint256 sellerAssets1) = take(units, lender, borrowerOffer); - assertEq(midnight.consumed(borrower, borrowerOffer.group), sellerAssets1, "consumed after first take"); - - (, uint256 sellerAssets2) = take(units, lender, borrowerOffer); - assertEq( - midnight.consumed(borrower, borrowerOffer.group), - sellerAssets1 + sellerAssets2, - "consumed after second take" - ); - } - - function testMaxAssetsBuyerConsumedAccumulates(uint256 units) public { - units = bound(units, 1, maxAssets / 2); - deal(address(loanToken), lender, type(uint128).max); - collateralize(market, borrower, 2 * units); - - lenderOffer.maxUnits = 0; - lenderOffer.maxAssets = type(uint128).max; - - (uint256 buyerAssets1,) = take(units, borrower, lenderOffer); - assertEq(midnight.consumed(lender, lenderOffer.group), buyerAssets1, "consumed after first take"); - - (uint256 buyerAssets2,) = take(units, borrower, lenderOffer); - assertEq( - midnight.consumed(lender, lenderOffer.group), buyerAssets1 + buyerAssets2, "consumed after second take" - ); - } - - function testMaxUnitsConsumedAccumulates(uint256 units) public { - units = bound(units, 1, maxAssets / 2); - deal(address(loanToken), lender, type(uint128).max); - collateralize(market, borrower, 2 * units); - - // Default offer caps: maxUnits set, maxAssets == 0 -> consumed tracks units. - take(units, lender, borrowerOffer); - assertEq(midnight.consumed(borrower, borrowerOffer.group), units, "consumed after first take"); - - take(units, lender, borrowerOffer); - assertEq(midnight.consumed(borrower, borrowerOffer.group), 2 * units, "consumed after second take"); - } - function testMaxAssetsZeroMeansNoLimitForSeller(uint256 units) public { units = bound(units, 1, maxAssets); deal(address(loanToken), lender, units); @@ -1117,7 +1063,7 @@ contract TakeTest is BaseTest { vm.prank(maker); midnight.setIsAuthorized(address(ratifier), true, maker); - vm.expectRevert(IMidnight.RatifierFailed.selector); + vm.expectRevert(IMidnight.RatifierFail.selector); vm.prank(sender); midnight.take(lenderOffer, hex"", 0, sender, sender, address(0), hex""); } diff --git a/test/TickLibTest.sol b/test/TickLibTest.sol index 1f839741c..f19d71451 100644 --- a/test/TickLibTest.sol +++ b/test/TickLibTest.sol @@ -5,22 +5,17 @@ import {BaseTest} from "./BaseTest.sol"; import {console} from "forge-std/Test.sol"; import {TickLib} from "../src/libraries/TickLib.sol"; import {UtilsLib} from "../src/libraries/UtilsLib.sol"; -import {MAX_TICK, PRICE_ROUNDING_STEP} from "../src/libraries/TickLib.sol"; +import {MAX_TICK} from "../src/libraries/TickLib.sol"; contract TickLibTest is BaseTest { using UtilsLib for uint256; // Tick to price - function testPriceRoundingStep() public pure { - assertEq(PRICE_ROUNDING_STEP, 0.0000001e18); - } - function testTickToPriceMinMax() public pure { - assertEq(MAX_TICK, 6744, "max tick"); assertEq(TickLib.tickToPrice(0), 0, "tick 0"); - assertEq(TickLib.tickToPrice(2), 0.0000001e18, "first non-zero tick"); - assertEq(TickLib.tickToPrice(MAX_TICK - 2), 1e18 - 0.0000001e18, "tick max - 2 just below par"); + assertEq(TickLib.tickToPrice(2), 1e12, "first non-zero tick"); + assertEq(TickLib.tickToPrice(MAX_TICK - 2), 1e18 - 1e12, "tick max - 2 just below par"); assertEq(TickLib.tickToPrice(MAX_TICK), 1e18, "tick max"); } @@ -134,8 +129,8 @@ contract TickLibTest is BaseTest { totalRelErrorWad += relErrorWad; maxRelErrorWad = max(maxRelErrorWad, relErrorWad); - // 3-term Taylor in wExp yields sub-2 bps absolute error. - assertLe(absErrorWad, 0.0002e18, string.concat("Tick ", vm.toString(tick), " error exceeds 2 bps")); + // 3-term Taylor in wExp yields max ~1.4 bps absolute error; 2 bps threshold leaves headroom. + assertLe(absErrorWad, 0.00014e18, string.concat("Tick ", vm.toString(tick), " error exceeds 2 bps")); if (solPrice > 0.01e18) { assertLe(relErrorWad, 0.0007e18, string.concat("Tick ", vm.toString(tick), " error exceeds 7 bps")); } diff --git a/test/frontend/sign-root.ts b/test/frontend/sign-root.ts index 5235bed28..acc7bac69 100644 --- a/test/frontend/sign-root.ts +++ b/test/frontend/sign-root.ts @@ -54,7 +54,6 @@ function buildTypes(height: number) { { name: "reduceOnly", type: "bool" }, { name: "maxUnits", type: "uint256" }, { name: "maxAssets", type: "uint256" }, - { name: "continuousFeeCap", type: "uint256" }, ], }; } @@ -82,7 +81,6 @@ function defaultOffer(number: string) { reduceOnly: false, maxUnits: "0", maxAssets: "0", - continuousFeeCap: "0", }; } diff --git a/test/ticks_exact.json b/test/ticks_exact.json index a81e0802b..ffbf46848 100644 --- a/test/ticks_exact.json +++ b/test/ticks_exact.json @@ -1,467 +1,5 @@ { "prices": [ - "49663775891", - "49912094758", - "50161655219", - "50412463483", - "50664525788", - "50917848404", - "51172437633", - "51428299808", - "51685441293", - "51943868486", - "52203587815", - "52464605741", - "52726928756", - "52990563385", - "53255516188", - "53521793755", - "53789402709", - "54058349708", - "54328641442", - "54600284634", - "54873286043", - "55147652458", - "55423390705", - "55700507643", - "55979010166", - "56258905201", - "56540199711", - "56822900693", - "57107015180", - "57392550240", - "57679512975", - "57967910523", - "58257750058", - "58549038792", - "58841783968", - "59135992871", - "59431672818", - "59728831164", - "60027475302", - "60327612660", - "60629250705", - "60932396940", - "61237058906", - "61543244182", - "61850960384", - "62160215167", - "62471016223", - "62783371285", - "63097288121", - "63412774542", - "63729838394", - "64048487566", - "64368729983", - "64690573612", - "65014026459", - "65339096570", - "65665792032", - "65994120970", - "66324091553", - "66655711989", - "66988990527", - "67323935457", - "67660555111", - "67998857864", - "68338852130", - "68680546367", - "69023949075", - "69369068796", - "69715914116", - "70064493662", - "70414816106", - "70766890162", - "71120724587", - "71476328185", - "71833709800", - "72192878323", - "72553842689", - "72916611876", - "73281194908", - "73647600856", - "74015838833", - "74385918000", - "74757847562", - "75131636771", - "75507294927", - "75884831373", - "76264255501", - "76645576749", - "77028804603", - "77413948597", - "77801018309", - "78190023371", - "78580973457", - "78973878293", - "79368747653", - "79765591360", - "80164419285", - "80565241349", - "80968067523", - "81372907827", - "81779772333", - "82188671161", - "82599614483", - "83012612521", - "83427675549", - "83844813892", - "84264037926", - "84685358080", - "85108784835", - "85534328722", - "85962000329", - "86391810294", - "86823769308", - "87257888116", - "87694177519", - "88132648368", - "88573311570", - "89016178089", - "89461258940", - "89908565194", - "90358107979", - "90809898478", - "91263947929", - "91720267627", - "92178868923", - "92639763225", - "93102961998", - "93568476764", - "94036319104", - "94506500655", - "94979033114", - "95453928234", - "95931197829", - "96410853772", - "96892907994", - "97377372487", - "97864259302", - "98353580550", - "98845348404", - "99339575097", - "99836272923", - "100335454238", - "100837131458", - "101341317065", - "101848023598", - "102357263664", - "102869049930", - "103383395126", - "103900312048", - "104419813554", - "104941912567", - "105466622075", - "105993955129", - "106523924848", - "107056544416", - "107591827080", - "108129786157", - "108670435029", - "109213787145", - "109759856021", - "110308655240", - "110860198456", - "111414499386", - "111971571821", - "112531429617", - "113094086701", - "113659557070", - "114227854791", - "114798993999", - "115372988903", - "115949853781", - "116529602982", - "117112250929", - "117697812114", - "118286301105", - "118877732541", - "119472121132", - "120069481666", - "120669829002", - "121273178074", - "121879543890", - "122488941535", - "123101386168", - "123716893022", - "124335477410", - "124957154720", - "125581940415", - "126209850038", - "126840899208", - "127475103623", - "128112479060", - "128753041372", - "129396806496", - "130043790444", - "130694009312", - "131347479272", - "132004216582", - "132664237577", - "133327558677", - "133994196381", - "134664167272", - "135337488018", - "136014175366", - "136694246150", - "137377717286", - "138064605778", - "138754928711", - "139448703258", - "140145946677", - "140846676311", - "141550909593", - "142258664040", - "142969957259", - "143684806943", - "144403230874", - "145125246923", - "145850873052", - "146580127310", - "147313027839", - "148049592869", - "148789840723", - "149533789816", - "150281458652", - "151032865832", - "151788030047", - "152546970081", - "153309704815", - "154076253220", - "154846634367", - "155620867419", - "156398971634", - "157180966369", - "157966871077", - "158756705307", - "159550488707", - "160348241023", - "161149982098", - "161955731878", - "162765510406", - "163579337825", - "164397234380", - "165219220416", - "166045316381", - "166875542824", - "167709920398", - "168548469859", - "169391212065", - "170238167982", - "171089358676", - "171944805322", - "172804529200", - "173668551696", - "174536894303", - "175409578621", - "176286626360", - "177168059336", - "178053899475", - "178944168813", - "179838889496", - "180738083781", - "181641774035", - "182549982740", - "183462732486", - "184380045979", - "185301946038", - "186228455596", - "187159597700", - "188095395512", - "189035872312", - "189981051494", - "190930956570", - "191885611170", - "192845039041", - "193809264049", - "194778310181", - "195752201541", - "196730962356", - "197714616973", - "198703189862", - "199696705613", - "200695188940", - "201698664683", - "202707157802", - "203720693384", - "204739296642", - "205762992915", - "206791807667", - "207825766490", - "208864895106", - "209909219362", - "210958765237", - "212013558840", - "213073626408", - "214138994312", - "215209689053", - "216285737266", - "217367165717", - "218454001308", - "219546271075", - "220644002188", - "221747221955", - "222855957817", - "223970237357", - "225090088292", - "226215538478", - "227346615914", - "228483348733", - "229625765215", - "230773893776", - "231927762977", - "233087401522", - "234252838256", - "235424102172", - "236601222404", - "237784228235", - "238973149092", - "240168014551", - "241368854333", - "242575698312", - "243788576508", - "245007519092", - "246232556386", - "247463718863", - "248701037150", - "249944542025", - "251194264421", - "252450235426", - "253712486283", - "254981048391", - "256255953306", - "257537232743", - "258824918573", - "260119042829", - "261419637704", - "262726735549", - "264040368880", - "265360570374", - "266687372872", - "268020809379", - "269360913064", - "270707717265", - "272061255483", - "273421561389", - "274788668820", - "276162611785", - "277543424460", - "278931141196", - "280325796511", - "281727425098", - "283136061825", - "284551741731", - "285974500033", - "287404372122", - "288841393568", - "290285600116", - "291737027694", - "293195712404", - "294661690534", - "296134998551", - "297615673103", - "299103751023", - "300599269329", - "302102265222", - "303612776089", - "305130839506", - "306656493236", - "308189775230", - "309730723628", - "311279376765", - "312835773161", - "314399951535", - "315971950796", - "317551810049", - "319139568592", - "320735265923", - "322338941736", - "323950635923", - "325570388575", - "327198239985", - "328834230647", - "330478401257", - "332130792715", - "333791446124", - "335460402795", - "337137704243", - "338823392193", - "340517508577", - "342220095537", - "343931195427", - "345650850809", - "347379104463", - "349115999379", - "350861578763", - "352615886039", - "354378964844", - "356150859037", - "357931612695", - "359721270115", - "361519875815", - "363327474537", - "365144111247", - "366969831133", - "368804679612", - "370648702327", - "372501945148", - "374364454176", - "376236275743", - "378117456410", - "380008042974", - "381908082463", - "383817622143", - "385736709513", - "387665392313", - "389603718519", - "391551736349", - "393509494261", - "395477040954", - "397454425373", - "399441696706", - "401438904387", - "403446098100", - "405463327772", - "407490643585", - "409528095968", - "411575735606", - "413633613432", - "415701780640", - "417780288675", - "419869189241", - "421968534301", - "424078376078", - "426198767055", - "428329759977", - "430471407855", - "432623763963", - "434786881843", - "436960815302", - "439145618419", - "441341345542", - "443548051291", - "445765790559", - "447994618513", - "450234590597", - "452485762532", - "454748190315", - "457021930228", - "459307038829", - "461603572963", - "463911589758", - "466231146625", - "468562301266", - "470905111669", - "473259636113", - "475625933168", - "478004061697", - "480394080857", - "482796050102", - "485210029181", - "487636078144", - "490074257340", - "492524627420", - "494987249338", "497462184353", "499949494032", "502449240246", @@ -6282,468 +5820,6 @@ "999999495038514816", "999999497550759808", "999999500050505856", - "999999502537815552", - "999999505012750720", - "999999507475372544", - "999999509925742720", - "999999512363921792", - "999999514789970816", - "999999517203949952", - "999999519605919104", - "999999521995938304", - "999999524374066816", - "999999526740363776", - "999999529094888320", - "999999531437698816", - "999999533768853376", - "999999536088410240", - "999999538396427008", - "999999540692961152", - "999999542978069888", - "999999545251809664", - "999999547514237440", - "999999549765409536", - "999999552005381504", - "999999554234209536", - "999999556451948544", - "999999558658654336", - "999999560854381568", - "999999563039184640", - "999999565213118208", - "999999567376236032", - "999999569528592000", - "999999571670240000", - "999999573801232896", - "999999575921624064", - "999999578031465600", - "999999580130810752", - "999999582219711488", - "999999584298219264", - "999999586366386560", - "999999588424264448", - "999999590471904000", - "999999592509356544", - "999999594536672256", - "999999596553901952", - "999999598561095552", - "999999600558303232", - "999999602545574784", - "999999604522958976", - "999999606490505728", - "999999608448263680", - "999999610396281472", - "999999612334607744", - "999999614263290624", - "999999616182377728", - "999999618091917440", - "999999619991956992", - "999999621882543488", - "999999623763724160", - "999999625635545728", - "999999627498054912", - "999999629351297792", - "999999631195320448", - "999999633030168960", - "999999634855888768", - "999999636672525568", - "999999638480124160", - "999999640278729856", - "999999642068387200", - "999999643849140992", - "999999645621035008", - "999999647384113920", - "999999649138421248", - "999999650884000512", - "999999652620895488", - "999999654349149184", - "999999656068804608", - "999999657779904512", - "999999659482491392", - "999999661176607744", - "999999662862295808", - "999999664539597184", - "999999666208553856", - "999999667869207296", - "999999669521598720", - "999999671165769216", - "999999672801760000", - "999999674429611392", - "999999676049364096", - "999999677661058176", - "999999679264734208", - "999999680860431360", - "999999682448189824", - "999999684028049152", - "999999685600048384", - "999999687164226944", - "999999688720623104", - "999999690269276288", - "999999691810224640", - "999999693343506816", - "999999694869160576", - "999999696387224064", - "999999697897734784", - "999999699400730752", - "999999700896248832", - "999999702384327040", - "999999703865001472", - "999999705338309376", - "999999706804287616", - "999999708262972160", - "999999709714399872", - "999999711158606464", - "999999712595627904", - "999999714025500032", - "999999715448258176", - "999999716863938048", - "999999718272574976", - "999999719674203392", - "999999721068858752", - "999999722456575488", - "999999723837388160", - "999999725211331072", - "999999726578438528", - "999999727938744576", - "999999729292282624", - "999999730639086976", - "999999731979190528", - "999999733312627200", - "999999734639429632", - "999999735959631104", - "999999737273264512", - "999999738580362240", - "999999739880957184", - "999999741175081344", - "999999742462767232", - "999999743744046592", - "999999745018951680", - "999999746287513600", - "999999747549764608", - "999999748805735552", - "999999750055457920", - "999999751298962816", - "999999752536281088", - "999999753767443712", - "999999754992480896", - "999999756211423488", - "999999757424301568", - "999999758631145600", - "999999759831985408", - "999999761026850944", - "999999762215771776", - "999999763398777600", - "999999764575897984", - "999999765747161728", - "999999766912598528", - "999999768072237056", - "999999769226106240", - "999999770374234752", - "999999771516651392", - "999999772653384192", - "999999773784461568", - "999999774909911680", - "999999776029762688", - "999999777144042240", - "999999778252777984", - "999999779355997824", - "999999780453728896", - "999999781545998592", - "999999782632834304", - "999999783714262784", - "999999784790310912", - "999999785861005568", - "999999786926373632", - "999999787986441216", - "999999789041234688", - "999999790090780544", - "999999791135104896", - "999999792174233472", - "999999793208192256", - "999999794237007104", - "999999795260703360", - "999999796279306496", - "999999797292842240", - "999999798301335424", - "999999799304811008", - "999999800303294336", - "999999801296810112", - "999999802285383168", - "999999803269037696", - "999999804247798528", - "999999805221689856", - "999999806190735872", - "999999807154961024", - "999999808114388736", - "999999809069043456", - "999999810018948608", - "999999810964127744", - "999999811904604544", - "999999812840402304", - "999999813771544448", - "999999814698053888", - "999999815619954176", - "999999816537267584", - "999999817450017280", - "999999818358226048", - "999999819261916160", - "999999820161110528", - "999999821055831168", - "999999821946100608", - "999999822831940736", - "999999823713373568", - "999999824590421504", - "999999825463105792", - "999999826331448192", - "999999827195470720", - "999999828055194752", - "999999828910641280", - "999999829761832064", - "999999830608787840", - "999999831451530112", - "999999832290079488", - "999999833124457216", - "999999833954683648", - "999999834780779648", - "999999835602765696", - "999999836420662272", - "999999837234489728", - "999999838044268032", - "999999838850017920", - "999999839651759104", - "999999840449511296", - "999999841243294720", - "999999842033128960", - "999999842819033600", - "999999843601028224", - "999999844379132544", - "999999845153365632", - "999999845923746688", - "999999846690295296", - "999999847453030016", - "999999848211970048", - "999999848967134208", - "999999849718541312", - "999999850466210176", - "999999851210159232", - "999999851950407168", - "999999852686972160", - "999999853419872768", - "999999854149127040", - "999999854874753024", - "999999855596769024", - "999999856315193088", - "999999857030042752", - "999999857741335808", - "999999858449090432", - "999999859153323776", - "999999859854053248", - "999999860551296640", - "999999861245071232", - "999999861935394176", - "999999862622282752", - "999999863305753856", - "999999863985824640", - "999999864662512000", - "999999865335832832", - "999999866005803520", - "999999866672441216", - "999999867335762432", - "999999867995783424", - "999999868652520832", - "999999869305990528", - "999999869956209664", - "999999870603193472", - "999999871246958592", - "999999871887520896", - "999999872524896512", - "999999873159100800", - "999999873790150016", - "999999874418059648", - "999999875042845184", - "999999875664522624", - "999999876283107072", - "999999876898613888", - "999999877511058432", - "999999878120456192", - "999999878726821888", - "999999879330170880", - "999999879930518272", - "999999880527879040", - "999999881122267392", - "999999881713698816", - "999999882302187904", - "999999882887749120", - "999999883470396928", - "999999884050146176", - "999999884627011072", - "999999885201005952", - "999999885772145280", - "999999886340442880", - "999999886905913344", - "999999887468570240", - "999999888028428032", - "999999888585500544", - "999999889139801472", - "999999889691344896", - "999999890240144000", - "999999890786212864", - "999999891329564928", - "999999891870213888", - "999999892408173056", - "999999892943455616", - "999999893476075264", - "999999894006044928", - "999999894533377920", - "999999895058087424", - "999999895580186368", - "999999896099688064", - "999999896616604800", - "999999897130950016", - "999999897642736256", - "999999898151976320", - "999999898658682880", - "999999899162868608", - "999999899664545664", - "999999900163727232", - "999999900660424832", - "999999901154651648", - "999999901646419456", - "999999902135740672", - "999999902622627584", - "999999903107091968", - "999999903589146240", - "999999904068802176", - "999999904546071680", - "999999905020966784", - "999999905493499392", - "999999905963680768", - "999999906431523072", - "999999906897037952", - "999999907360236672", - "999999907821131136", - "999999908279732352", - "999999908736052096", - "999999909190101504", - "999999909641892096", - "999999910091434880", - "999999910538741120", - "999999910983821952", - "999999911426688512", - "999999911867351680", - "999999912305822464", - "999999912742111872", - "999999913176230656", - "999999913608189824", - "999999914037999616", - "999999914465671296", - "999999914891215232", - "999999915314642048", - "999999915735961984", - "999999916155186176", - "999999916572324480", - "999999916987387392", - "999999917400385408", - "999999917811328896", - "999999918220227712", - "999999918627092224", - "999999919031932544", - "999999919434758656", - "999999919835580672", - "999999920234408704", - "999999920631252224", - "999999921026121728", - "999999921419026432", - "999999921809976576", - "999999922198981760", - "999999922586051456", - "999999922971195520", - "999999923354423296", - "999999923735744512", - "999999924115168640", - "999999924492705024", - "999999924868363136", - "999999925242152320", - "999999925614081920", - "999999925984161152", - "999999926352399232", - "999999926718805120", - "999999927083388160", - "999999927446157184", - "999999927807121792", - "999999928166290176", - "999999928523671808", - "999999928879275264", - "999999929233109760", - "999999929585183872", - "999999929935506304", - "999999930284085760", - "999999930630931200", - "999999930976050816", - "999999931319453696", - "999999931661147904", - "999999932001142144", - "999999932339444992", - "999999932676064512", - "999999933011009408", - "999999933344288000", - "999999933675908352", - "999999934005879040", - "999999934334208000", - "999999934660903424", - "999999934985973632", - "999999935309426432", - "999999935631270016", - "999999935951512448", - "999999936270161536", - "999999936587225472", - "999999936902711936", - "999999937216628736", - "999999937528983808", - "999999937839784832", - "999999938149039744", - "999999938456755712", - "999999938762941056", - "999999939067603072", - "999999939370749440", - "999999939672387456", - "999999939972524672", - "999999940271168768", - "999999940568327296", - "999999940864007168", - "999999941158216064", - "999999941450961152", - "999999941742249856", - "999999942032089600", - "999999942320487040", - "999999942607449728", - "999999942892984832", - "999999943177099264", - "999999943459800192", - "999999943741094784", - "999999944020989952", - "999999944299492480", - "999999944576609152", - "999999944852347520", - "999999945126713984", - "999999945399715328", - "999999945671358464", - "999999945941650176", - "999999946210597248", - "999999946478206336", - "999999946744483712", - "999999947009436672", - "999999947273071232", - "999999947535394304", - "999999947796412160", - "999999948056131456", - "999999948314558720", - "999999948571700096", - "999999948827562368", - "999999949082151552", - "999999949335474176", - "999999949587536512", - "999999949838344832", - "999999950087905152", - "999999950336224000" + "999999502537815552" ] } \ No newline at end of file diff --git a/test/ticks_exact_gen.py b/test/ticks_exact_gen.py index c6f4bcec0..ed36710e3 100644 --- a/test/ticks_exact_gen.py +++ b/test/ticks_exact_gen.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import json -MAX_TICK = 6744 +MAX_TICK = 5820 def exact_price(tick): """Reference tick to price implementation.""" From 60fd9683f81ce53b1a895e5f8e0bcdc2d484c206 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Mon, 22 Jun 2026 11:19:31 +0200 Subject: [PATCH 11/18] evert "log more" This reverts commit e7c70aa37af4b288bf5ae4fe62d95585079076e8. --- src/Midnight.sol | 19 ++++--------------- src/libraries/EventsLib.sol | 3 +-- test/ContinuousFeeTest.sol | 19 ++++--------------- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/Midnight.sol b/src/Midnight.sol index 26fa7cbca..7f5ea675e 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -436,6 +436,9 @@ contract Midnight is IMidnight { id, units, taker, + offer.maker, + offer.buy, + offer.group, buyerAssets, sellerAssets, newConsumed, @@ -444,21 +447,7 @@ contract Midnight is IMidnight { buyerCreditIncrease, sellerCreditDecrease, receiver, - payer, - takerCallback - ); - - emit EventsLib.TakenOffer( - offer.maker, - offer.tick, - offer.start, - offer.expiry, - offer.reduceOnly, - offer.maxUnits, - offer.maxAssets, - offer.buy, - offer.group, - offer.callback + payer ); bool wasLocked = UtilsLib.tExchange(LIQUIDATION_LOCK_SLOT, id, seller, true); diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 19172b67e..ecdbc794e 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -19,8 +19,7 @@ library EventsLib { event SetDefaultContinuousFee(address indexed loanToken, uint256 newContinuousFee); event UpdatePosition(bytes32 indexed id_, address indexed user, uint256 creditDecrease, uint256 pendingFeeDecrease, uint256 accruedFee); event MarketCreated(Market market, bytes32 indexed id_); - event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback); - event TakenOffer(address indexed maker, uint256 tick, uint256 start, uint256 expiry, bool reduceOnly, uint256 maxUnits, uint256 maxAssets, bool buy, bytes32 group, address callback); + event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, address indexed maker, bool offerIsBuy, bytes32 group, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer); event Withdraw(address caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address indexed receiver, uint256 pendingFeeDecrease); event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer); event SupplyCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf); diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index ad339eb53..9b80ea47f 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -280,6 +280,9 @@ contract ContinuousFeeTest is BaseTest { id, exitAmount, lender, + otherLender, + true, + keccak256("lender-exit"), takeAssets, takeAssets, exitAmount, @@ -288,21 +291,7 @@ contract ContinuousFeeTest is BaseTest { exitAmount, exitAmount, lender, - otherLender, - address(0) - ); - vm.expectEmit(); - emit EventsLib.TakenOffer( - otherLender, - MAX_TICK, - 0, - vm.getBlockTimestamp(), - false, - exitAmount, - 0, - true, - keccak256("lender-exit"), - address(0) + otherLender ); take(exitAmount, lender, _makeBuyOffer(keccak256("lender-exit"))); // lender is taker = seller From 576c996a912d7f65eef86f8c8800ddafd7b90286 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Mon, 22 Jun 2026 14:26:31 +0200 Subject: [PATCH 12/18] Reapply origin/main after accidental revert --- certora/confs/PostMaturityDebt.conf | 10 +- certora/confs/ReentrancyView.conf | 1 + certora/specs/TickToPrice.spec | 4 +- src/Midnight.sol | 20 +- src/interfaces/IMidnight.sol | 22 +- src/libraries/TickLib.sol | 6 +- src/ratifiers/libraries/HashLib.sol | 47 +- test/BaseTest.sol | 3 + test/ContinuousFeeTest.sol | 111 ++++ test/FrontendSignatureTest.sol | 6 +- test/HashLibTest.sol | 2 +- test/LiquidationTest.sol | 18 + test/OtherFunctionsTest.sol | 39 ++ test/SettersTest.sol | 8 +- test/TakeAmountsTest.sol | 8 +- test/TakeTest.sol | 56 +- test/TickLibTest.sol | 15 +- test/frontend/sign-root.ts | 2 + test/ticks_exact.json | 926 +++++++++++++++++++++++++++- test/ticks_exact_gen.py | 2 +- 20 files changed, 1235 insertions(+), 71 deletions(-) diff --git a/certora/confs/PostMaturityDebt.conf b/certora/confs/PostMaturityDebt.conf index bf82544a8..231f1040a 100644 --- a/certora/confs/PostMaturityDebt.conf +++ b/certora/confs/PostMaturityDebt.conf @@ -12,13 +12,9 @@ "optimistic_hashing": true, "hashing_length_bound": 2048, "prover_args": [ - "-mediumTimeout 60", - "-timeout 3600", - "-oldSplitParallel true", - "-dontStopAtFirstSplitTimeout true", - "-splitParallelTimelimit 7000", - "-splitParallelInitialDepth 3", - "-numOfParallelSplits 7" + "-destructiveOptimizations twostage", + "-s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" ], + "smt_timeout": 7200, "msg": "Midnight Post-Maturity Debt" } diff --git a/certora/confs/ReentrancyView.conf b/certora/confs/ReentrancyView.conf index c7c44696a..32dd95279 100644 --- a/certora/confs/ReentrancyView.conf +++ b/certora/confs/ReentrancyView.conf @@ -14,5 +14,6 @@ "loop_iter": 5, "optimistic_hashing": true, "hashing_length_bound": 2048, + "smt_timeout": 7200, "msg": "Midnight Reentrancy View" } diff --git a/certora/specs/TickToPrice.spec b/certora/specs/TickToPrice.spec index 619f94712..05ae46f02 100644 --- a/certora/specs/TickToPrice.spec +++ b/certora/specs/TickToPrice.spec @@ -30,7 +30,7 @@ rule wExpCasting(uint256 x) { definition maxInput() returns int256 = assert_int256(lnOnePlusDelta() * (maxTick() / 2)); -definition maxOutput() returns uint256 = 2010201770916298901946368; +definition maxOutput() returns uint256 = 20134595243400876315901952; rule maxOutputIsWExpOfMaxInput() { assert maxOutput() == wExp(maxInput()); @@ -104,7 +104,7 @@ rule wExpIsMonotonicOnPositiveRangeWhenQJumps(int256 x) { /// tickToPrice properties /// // Useful for PriceToTick.spec -definition cvlMaxTick() returns uint256 = 5820; +definition cvlMaxTick() returns uint256 = 6744; rule cvlMaxTickIsMaxTick() { assert cvlMaxTick() == maxTick(); diff --git a/src/Midnight.sol b/src/Midnight.sol index 7f5ea675e..2d3e2c4f3 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -51,8 +51,11 @@ import {IMidnight, Market, Offer, CollateralParams, MarketState, Position} from /// CONTINUOUS FEES /// @dev A default continuous fee (per loan token) is set on new markets. Then, the fee setter can override it. /// @dev The fee is tracked per lender via pendingFee in each position. If the market's continuous fee changes, the -/// pending fee of existing lenders is not updated (=> their fee is fixed). +/// pending fee of existing lenders is not updated (=> their fee is fixed). If the market's continuious fee is decreased +/// lenders might self-take to exit and re-enter to reduce their pending fee (at the cost of the settlement fee). /// @dev In the absence of bad debt realizations, the face value of a lender's position is credit - pendingFee. +/// @dev An offer cannot be taken if its continuousFeeCap value is lower than the current market continuous fee. +/// This ensures maker buyers can protect against future continuous fee increases. /// /// LIQUIDATIONS /// @dev Accounts are liquidatable only if they are either unhealthy or the maturity has passed. The liquidation @@ -73,6 +76,8 @@ import {IMidnight, Market, Offer, CollateralParams, MarketState, Position} from /// minNewCollateral * liquidatedCollatPrice / LIF < rcfThreshold /// <=> (collateral - maxRepaid * LIF / liquidatedCollatPrice) * liquidatedCollatPrice / LIF < rcfThreshold /// <=> collateral * liquidatedCollatPrice / LIF - maxRepaid < rcfThreshold +/// @dev Nothing prevents borrowers to open small positions / liquidators to leave small positions that might not be +/// profitable to liquidate because of gas cost. The RCF deactivation at rcfThreshold just prevents the systemic aspect. /// @dev In the "post-maturity mode", the LIF (liquidation incentive factor) grows linearly from 1 at maturity to maxLif /// at maturity + TIME_TO_MAX_LIF, and the RCF is deactivated. /// @dev In both modes, maxLif is used to determine if the account has some bad debt, to always assume the worst case. @@ -86,7 +91,7 @@ import {IMidnight, Market, Offer, CollateralParams, MarketState, Position} from /// @dev To work as expected, all offers in the same group should have the same direction (offer.buy), max values and /// loan token. /// -/// OFFER CAPS +/// OFFER SIZE /// @dev Exactly one of maxAssets or maxUnits must be nonzero per offer (take reverts otherwise). /// @dev maxAssets caps max buyer assets if offer.buy is true, and caps max seller assets otherwise. /// @dev If maxAssets > 0, assets are capped to maxAssets, otherwise units are capped to maxUnits. @@ -263,7 +268,7 @@ contract Midnight is IMidnight { MarketState storage _marketState = marketState[id]; require(msg.sender == feeSetter, OnlyFeeSetter()); require(index <= 6, InvalidFeeIndex()); - require(newSettlementFee <= maxSettlementFee(index), SettlementFeeTooHigh()); + require(newSettlementFee <= maxSettlementFee(index), SettlementFeeAboveMax()); require(newSettlementFee % CBP == 0, FeeNotMultipleOfFeeCbp()); require(_marketState.tickSpacing > 0, MarketNotCreated()); // forge-lint: disable-next-item(unsafe-typecast) as newSettlementFee <= maxSettlementFee <= uint16.max * CBP @@ -281,7 +286,7 @@ contract Midnight is IMidnight { function setDefaultSettlementFee(address loanToken, uint256 index, uint256 newSettlementFee) external { require(msg.sender == feeSetter, OnlyFeeSetter()); require(index <= 6, InvalidFeeIndex()); - require(newSettlementFee <= maxSettlementFee(index), SettlementFeeTooHigh()); + require(newSettlementFee <= maxSettlementFee(index), SettlementFeeAboveMax()); require(newSettlementFee % CBP == 0, FeeNotMultipleOfFeeCbp()); // forge-lint: disable-next-item(unsafe-typecast) as newSettlementFee <= maxSettlementFee <= uint16.max * CBP defaultSettlementFeeCbp[loanToken][index] = uint16(newSettlementFee / CBP); @@ -291,7 +296,7 @@ contract Midnight is IMidnight { function setMarketContinuousFee(bytes32 id, uint256 newContinuousFee) external { MarketState storage _marketState = marketState[id]; require(msg.sender == feeSetter, OnlyFeeSetter()); - require(newContinuousFee <= MAX_CONTINUOUS_FEE, ContinuousFeeTooHigh()); + require(newContinuousFee <= MAX_CONTINUOUS_FEE, ContinuousFeeAboveMax()); require(_marketState.tickSpacing > 0, MarketNotCreated()); // forge-lint: disable-next-line(unsafe-typecast) as newContinuousFee <= MAX_CONTINUOUS_FEE < type(uint32).max _marketState.continuousFee = uint32(newContinuousFee); @@ -300,7 +305,7 @@ contract Midnight is IMidnight { function setDefaultContinuousFee(address loanToken, uint256 newContinuousFee) external { require(msg.sender == feeSetter, OnlyFeeSetter()); - require(newContinuousFee <= MAX_CONTINUOUS_FEE, ContinuousFeeTooHigh()); + require(newContinuousFee <= MAX_CONTINUOUS_FEE, ContinuousFeeAboveMax()); // forge-lint: disable-next-line(unsafe-typecast) as newContinuousFee <= MAX_CONTINUOUS_FEE < type(uint32).max defaultContinuousFee[loanToken] = uint32(newContinuousFee); emit EventsLib.SetDefaultContinuousFee(loanToken, newContinuousFee); @@ -352,6 +357,7 @@ contract Midnight is IMidnight { MarketState storage _marketState = marketState[id]; require(_marketState.lossFactor < type(uint128).max, MarketLossFactorMaxedOut()); require((offer.maxAssets == 0) != (offer.maxUnits == 0), InvalidOfferCaps()); + require(_marketState.continuousFee <= offer.continuousFeeCap, ContinuousFeeAboveOfferCap()); require(offer.tick % _marketState.tickSpacing == 0, TickNotAccessible()); require(block.timestamp >= offer.start, OfferNotStarted()); require(block.timestamp <= offer.expiry, OfferExpired()); @@ -361,7 +367,7 @@ contract Midnight is IMidnight { UnusedReceiverMustBeZero() ); require(isAuthorized[offer.maker][offer.ratifier], RatifierUnauthorized()); - require(IRatifier(offer.ratifier).isRatified(offer, ratifierData) == CALLBACK_SUCCESS, RatifierFail()); + require(IRatifier(offer.ratifier).isRatified(offer, ratifierData) == CALLBACK_SUCCESS, RatifierFailed()); uint256 offerPrice = TickLib.tickToPrice(offer.tick); uint256 timeToMaturity = UtilsLib.zeroFloorSub(offer.market.maturity, block.timestamp); diff --git a/src/interfaces/IMidnight.sol b/src/interfaces/IMidnight.sol index eb5af676c..5f27fe0bc 100644 --- a/src/interfaces/IMidnight.sol +++ b/src/interfaces/IMidnight.sol @@ -33,6 +33,7 @@ struct Offer { bool reduceOnly; uint256 maxUnits; uint256 maxAssets; // buyerAssets if offer.buy else sellerAssets + uint256 continuousFeeCap; } /// @dev Settlement fee cbp values and the continuous fee are 0 until the market is created, then set to the default @@ -71,14 +72,10 @@ interface IMidnight { error CollateralParamsNotSorted(); error ConsumedAssets(); error ConsumedUnits(); - error ContinuousFeeTooHigh(); + error ContinuousFeeAboveMax(); + error ContinuousFeeAboveOfferCap(); error FeeNotMultipleOfFeeCbp(); error InconsistentInput(); - error WrongBuyCallbackReturnValue(); - error WrongSellCallbackReturnValue(); - error WrongRepayCallbackReturnValue(); - error WrongLiquidateCallbackReturnValue(); - error WrongFlashLoanCallbackReturnValue(); error InvalidFeeIndex(); error InvalidMaxLif(); error InvalidOfferCaps(); @@ -86,32 +83,37 @@ interface IMidnight { error LiquidatorGatedFromLiquidating(); error LltvNotAllowed(); error MakerCreditOrDebtIncreased(); + error MarketLossFactorMaxedOut(); + error MarketNotCreated(); error MaturityTooFar(); error NoCollateralParams(); error NotBorrower(); error NotLiquidatable(); - error MarketLossFactorMaxedOut(); - error MarketNotCreated(); error OfferExpired(); error OfferNotStarted(); error OnlyFeeClaimer(); error OnlyFeeSetter(); error OnlyRoleSetter(); error OnlyTickSpacingSetter(); - error RatifierFail(); + error RatifierFailed(); error RatifierUnauthorized(); error RecoveryCloseFactorConditionsViolated(); error SelfTake(); error SellerGatedFromIncreasingDebt(); error SellerIsLiquidatable(); + error SettlementFeeAboveMax(); error TakerUnauthorized(); error TickNotAccessible(); error TooManyActivatedCollaterals(); error TooManyCollateralParams(); - error SettlementFeeTooHigh(); error Unauthorized(); error UnhealthyBorrower(); error UnusedReceiverMustBeZero(); + error WrongBuyCallbackReturnValue(); + error WrongFlashLoanCallbackReturnValue(); + error WrongLiquidateCallbackReturnValue(); + error WrongRepayCallbackReturnValue(); + error WrongSellCallbackReturnValue(); // forgefmt: disable-start /// IMMUTABLES /// diff --git a/src/libraries/TickLib.sol b/src/libraries/TickLib.sol index e71872b6c..9f72b4089 100644 --- a/src/libraries/TickLib.sol +++ b/src/libraries/TickLib.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.0; int256 constant LN_ONE_PLUS_DELTA = 0.004987541511039073e18; // floor(ln(1.005) * 1e18) -uint256 constant MAX_TICK = 5820; -// Minimum representable price increment in WAD (1e-6 WAD). Tick prices are rounded to multiples of this value. -uint256 constant PRICE_ROUNDING_STEP = 1e12; +uint256 constant MAX_TICK = 6744; +// Minimum representable price increment in WAD (1e-7 WAD). Tick prices are rounded to multiples of this value. +uint256 constant PRICE_ROUNDING_STEP = 1e11; library TickLib { using TickLib for uint256; diff --git a/src/ratifiers/libraries/HashLib.sol b/src/ratifiers/libraries/HashLib.sol index a377b16ea..13d8be011 100644 --- a/src/ratifiers/libraries/HashLib.sol +++ b/src/ratifiers/libraries/HashLib.sol @@ -9,7 +9,7 @@ bytes32 constant COLLATERAL_PARAMS_TYPEHASH = 0xaf44a88eb50ebdbbebd980e5a23045c4 /// @dev keccak256(bytes.concat(MARKET_TYPE, COLLATERAL_PARAMS_TYPE)). bytes32 constant MARKET_TYPEHASH = 0x358117e98511cc3df97175dca58053b06675b43ad090b0553f8a1eff008b6e2e; /// @dev keccak256(bytes.concat(OFFER_TYPE, COLLATERAL_PARAMS_TYPE, MARKET_TYPE)). -bytes32 constant OFFER_TYPEHASH = 0x980a4cfc9766df84667f316d76e10cefc8caf04fb4cd4a9fca00a8e7b34f619c; +bytes32 constant OFFER_TYPEHASH = 0x6bd2a06ec6952feb97c3e3b4f7de6c342f12b1ac769d5c91368271af636c85b7; library HashLib { error LeafIndexOutOfRange(); @@ -21,28 +21,28 @@ library HashLib { /// @dev Reverts if height is greater than 20. function offerTreeTypeHash(uint256 height) internal pure returns (bytes32) { if (height <= 10) { - if (height == 0) return 0x2b9ee710e1977dfc5778fe18c905ccc1d9e144baf3ba83be732d4da65ecb73e3; - if (height == 1) return 0x3cc16189b92a85898f1d5c6e87282c8ded7c1c93b2323d5e85ae10c5f4b2b220; - if (height == 2) return 0x6de37d3e570afa293a8107d4b6b1d9547616c04f42164d009c89194787b2ffa6; - if (height == 3) return 0xba3ea2ddfbf40a906fcd1b9506dbd344c062e8dcba8b5c902ceb13339f45a358; - if (height == 4) return 0xe5faa865e93bc1b7b8fdf91980f54682d649683b014edd6c54b642f5a0c96977; - if (height == 5) return 0xeda50f61dd2a827c6ff9fbfcd54335628dcaa78aaa4f2d118c60886219cdce2b; - if (height == 6) return 0x54e2c9cc40cdc0e9ad530cf2be298f952f57af2b18b02f88274a9bbab359d23a; - if (height == 7) return 0xc9d81859d60d6b21c688f4be93ca83e3be222728bb156ef5f4cf497f879f1e29; - if (height == 8) return 0xd59b0c4544e0c60c8611eab0aaa402575f14ee784d22289c5d57f48c422a62d6; - if (height == 9) return 0xccad21701f34f08bb8398a3dbc77e20e4c9c424930f3a8b31485bf059e2bdb20; - return 0x8a42dfb49807647bfc49c906aef322aa0239d40e4cb675761e141bc7bfa530da; + if (height == 0) return 0xc27c38e446b48c820ab9c4373dc63a4a750a08165cb4bb488206ebabe045d650; + if (height == 1) return 0x4e15d8736f4406e07bf9844b1653474472a827130c61e899bf1f574a88b8d987; + if (height == 2) return 0x46d107447b480c38ef5b7f54603dba0cb23b887f302b01a998b9d8a80320dd53; + if (height == 3) return 0xd1f3607a8e81454bb3baf5f898274ab47541fffc690278a74f13e174e116be72; + if (height == 4) return 0xb2d98adca9d116c9bc02ce59ec599ac3c2d33db1c0d1217c7e411d9198d427be; + if (height == 5) return 0x5931e0597fcf986027f3118b2495a9ac22139d133f9ad2c2198e6738dc3886c5; + if (height == 6) return 0x3967d37928614a085b47e8758fbc3869a8aed63bdf60ccee8536ff2b5064da06; + if (height == 7) return 0xd6b9f5f45915a260f6e521d9b40f86c385730b6bc330590fcde212e2fea64263; + if (height == 8) return 0x080caa519dbd5328c119d9907e0fa3d9a50dc2ae4bf6dd42c93c100dbc89b51a; + if (height == 9) return 0x45da471048924165ea2ad1855ba940e454b486e71dcf1666c71a928c8844c419; + return 0xa49a9434fc1836bd08097368325b31039b6a0fd44919f53e4d8f4bf814084cb0; } else { - if (height == 11) return 0x2adc0d948b2e3ecb642661590d2eec36d4e71e9acf382deb6574371800caf198; - if (height == 12) return 0xf5845dfaed016de272342f346346a49d4b1694f622144d420558a38e46ac9dad; - if (height == 13) return 0x3d7df854e6294bf433b64bbb8d0a82fa875a87b45b0016db27fc5752e54126ad; - if (height == 14) return 0x72a991a101708716ff427c524404ab44f4d4d1f4e7e76c0ae8b967222164b348; - if (height == 15) return 0x762c88fc52cf78a54401d247790f1bdb619d51d3458d1415c20d1422197cecc4; - if (height == 16) return 0x8ede2209e94c8d5f8379d733dc8712b71a3888c1c4b70f3d6b22285f70bf4286; - if (height == 17) return 0x425b18f07b3ac2f641977d2c294590565dd40b5d8414610568dca64628399975; - if (height == 18) return 0x7e7d98718c0180e882e5963b9bd49810096912c273dfa38d8afdd6d39fde86ec; - if (height == 19) return 0x8d35d491a29d846489e19688efff3c4cc7dbd54458058d49b30294074539f0b9; - if (height == 20) return 0x824e385eea1953bcbc783bf900b18aa6fba129b6908765e986cf0968b491ec4f; + if (height == 11) return 0xd3e93e4525132f0187a6964dc01fef33fde414538ffd212e9f2f478c3263e0a0; + if (height == 12) return 0x25990db2d26547f92c711988300df317af57bad5cd5d9d8e787a82f95c929474; + if (height == 13) return 0x8e0c648afa977572ead40a1d10a6db2c425b8099545006d834a7b849c6166643; + if (height == 14) return 0x4b635250efa6243e277fdd0cf6df993c2943b64f10f3a0756ceb1f47ef8f9b18; + if (height == 15) return 0xbde1c927f6222c07c8df264e68b42b8382c7c2b85f4729e0df94297cfeebfa91; + if (height == 16) return 0x4d58aea1a67f94be21ab1415bf3b602592430eb9112268fd0fc4e141b1a35e76; + if (height == 17) return 0x14c03281bce13010b158e5a4a3378be394ac9e16118aedb17d82ced51e66836c; + if (height == 18) return 0x99fd3e76f43b2cc221cb9860bc6c96cda95af3fa07ef5f04e071b54aa9386d06; + if (height == 19) return 0x1b1c2f1a04968094d8d0453d49838f7a809d1202ae04a1c2e0964e442ff7988b; + if (height == 20) return 0xc8ccd3cb3267dd76f563584920ac60f2283b719917481b85fe5e10b754932455; revert TreeTooHigh(); } } @@ -131,7 +131,8 @@ library HashLib { offer.ratifier, offer.reduceOnly, offer.maxUnits, - offer.maxAssets + offer.maxAssets, + offer.continuousFeeCap ) ); } diff --git a/test/BaseTest.sol b/test/BaseTest.sol index f4e818f76..ea494bdd9 100644 --- a/test/BaseTest.sol +++ b/test/BaseTest.sol @@ -183,6 +183,7 @@ abstract contract BaseTest is Test { lenderOffer.buy = true; lenderOffer.maker = otherLender; lenderOffer.maxUnits = type(uint256).max; + lenderOffer.continuousFeeCap = type(uint256).max; lenderOffer.group = keccak256(abi.encode("non zero group")); lenderOffer.ratifier = address(dummyRatifier); lenderOffer.expiry = vm.getBlockTimestamp() + 200; @@ -208,6 +209,7 @@ abstract contract BaseTest is Test { badBorrowerOffer.start = vm.getBlockTimestamp(); badBorrowerOffer.expiry = vm.getBlockTimestamp() + 200; badBorrowerOffer.tick = MAX_TICK; + badBorrowerOffer.continuousFeeCap = type(uint256).max; vm.prank(badBorrower); @@ -324,6 +326,7 @@ abstract contract BaseTest is Test { borrowerOffer.start = vm.getBlockTimestamp(); borrowerOffer.expiry = vm.getBlockTimestamp(); borrowerOffer.tick = MAX_TICK; + borrowerOffer.continuousFeeCap = type(uint256).max; } function max(uint256 a, uint256 b) internal pure returns (uint256) { diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 9b80ea47f..da6d0724e 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -59,6 +59,7 @@ contract ContinuousFeeTest is BaseTest { o.buy = true; o.maker = otherLender; o.maxUnits = type(uint256).max; + o.continuousFeeCap = type(uint256).max; o.ratifier = address(dummyRatifier); o.expiry = vm.getBlockTimestamp(); o.tick = MAX_TICK; @@ -193,6 +194,7 @@ contract ContinuousFeeTest is BaseTest { borrowOffer.start = vm.getBlockTimestamp(); borrowOffer.expiry = vm.getBlockTimestamp(); borrowOffer.tick = MAX_TICK; + borrowOffer.continuousFeeCap = type(uint256).max; } function testTwoLendersDifferentRates( @@ -437,6 +439,40 @@ contract ContinuousFeeTest is BaseTest { assertEq(midnight.withdrawable(id), withdrawableBefore - claimAmount, "withdrawable after claim"); } + function testClaimContinuousFeeDecrementsWithdrawable(uint256 credit, uint256 feeRate, uint256 ttm, uint256 elapsed) + public + { + credit = bound(credit, 1, MAX_CREDIT); + feeRate = bound(feeRate, 1, MAX_CONTINUOUS_FEE); + ttm = bound(ttm, 2, 360 days); + elapsed = bound(elapsed, 1, ttm - 1); + + setupLender(credit, feeRate, ttm); + + vm.warp(vm.getBlockTimestamp() + elapsed); + midnight.updatePosition(market, lender); + + uint256 feeAmount = midnight.continuousFeeCredit(id); + vm.assume(feeAmount > 1); + uint256 claimAmount = feeAmount / 2; // two claims stay within the accrued fee. + + // Repay so withdrawable covers the claims. + deal(address(loanToken), borrower, credit); + vm.prank(borrower); + midnight.repay(market, credit, borrower, address(0), hex""); + + address receiver = makeAddr("receiver"); + uint256 withdrawableBefore = midnight.withdrawable(id); + + vm.prank(feeClaimer); + midnight.claimContinuousFee(market, claimAmount, receiver); + assertEq(midnight.withdrawable(id), withdrawableBefore - claimAmount, "withdrawable after first claim"); + + vm.prank(feeClaimer); + midnight.claimContinuousFee(market, claimAmount, receiver); + assertEq(midnight.withdrawable(id), withdrawableBefore - 2 * claimAmount, "withdrawable after second claim"); + } + function testClaimContinuousFeeOnlyFeeClaimer(address caller) public { vm.assume(caller != feeClaimer); vm.prank(caller); @@ -537,4 +573,79 @@ contract ContinuousFeeTest is BaseTest { setupLender(1e18, 0, 100 days); assertEq(midnight.lastAccrual(id, makeAddr("nobody")), 0, "lastAccrual zero for fresh position"); } + + /// @dev Creates the market with continuousFee == feeRate (touch copies the default fee). + function _setupFeeMarket(uint256 feeRate) internal { + market.maturity = vm.getBlockTimestamp() + 100 days; + id = toId(market); + midnight.setDefaultContinuousFee(address(loanToken), feeRate); + midnight.touchMarket(market); + assertEq(midnight.continuousFee(id), feeRate, "market continuousFee"); + } + + function testTakeRevertsWhenBuyOfferContinuousFeeCapExceeded(uint256 feeRate, uint256 continuousFeeCap) public { + feeRate = bound(feeRate, 1, MAX_CONTINUOUS_FEE); + continuousFeeCap = bound(continuousFeeCap, 0, feeRate - 1); + _setupFeeMarket(feeRate); + + uint256 units = 1e18; + collateralize(market, borrower, units * 2); + deal(address(loanToken), otherLender, units); + + Offer memory offer = _makeBuyOffer(keccak256("buy-max-fee")); + offer.maxUnits = units; + offer.continuousFeeCap = continuousFeeCap; + + vm.expectRevert(IMidnight.ContinuousFeeAboveOfferCap.selector); + take(units, borrower, offer); // borrower is the seller, otherLender (maker) is the buyer. + } + + function testTakeSucceedsWhenBuyOfferContinuousFeeCapRespected(uint256 feeRate, uint256 continuousFeeCap) public { + feeRate = bound(feeRate, 0, MAX_CONTINUOUS_FEE); + continuousFeeCap = bound(continuousFeeCap, feeRate, type(uint256).max); + _setupFeeMarket(feeRate); + + uint256 units = 1e18; + collateralize(market, borrower, units * 2); + deal(address(loanToken), otherLender, units); + + Offer memory offer = _makeBuyOffer(keccak256("buy-ok-fee")); + offer.maxUnits = units; + offer.continuousFeeCap = continuousFeeCap; + + take(units, borrower, offer); + assertEq(midnight.debtOf(id, borrower), units, "borrower took on debt"); + } + + function testTakeRevertsWhenSellOfferContinuousFeeCapExceeded(uint256 feeRate, uint256 continuousFeeCap) public { + feeRate = bound(feeRate, 1, MAX_CONTINUOUS_FEE); + continuousFeeCap = bound(continuousFeeCap, 0, feeRate - 1); + _setupFeeMarket(feeRate); + + uint256 units = 1e18; + collateralize(market, otherBorrower, units * 2); + deal(address(loanToken), lender, units); + + Offer memory offer = _makeBorrowOffer(units); + offer.continuousFeeCap = continuousFeeCap; + + vm.expectRevert(IMidnight.ContinuousFeeAboveOfferCap.selector); + take(units, lender, offer); // lender is the buyer, otherBorrower (maker) is the seller. + } + + function testTakeSucceedsWhenSellOfferContinuousFeeCapRespected(uint256 feeRate, uint256 continuousFeeCap) public { + feeRate = bound(feeRate, 0, MAX_CONTINUOUS_FEE); + continuousFeeCap = bound(continuousFeeCap, feeRate, type(uint256).max); + _setupFeeMarket(feeRate); + + uint256 units = 1e18; + collateralize(market, otherBorrower, units * 2); + deal(address(loanToken), lender, units); + + Offer memory offer = _makeBorrowOffer(units); + offer.continuousFeeCap = continuousFeeCap; + + take(units, lender, offer); // lender is the buyer, otherBorrower (maker) is the seller. + assertEq(midnight.creditOf(id, lender), units, "lender gained credit"); + } } diff --git a/test/FrontendSignatureTest.sol b/test/FrontendSignatureTest.sol index 0f0d1527f..df675cbe5 100644 --- a/test/FrontendSignatureTest.sol +++ b/test/FrontendSignatureTest.sol @@ -9,10 +9,10 @@ import {CALLBACK_SUCCESS} from "../src/libraries/ConstantsLib.sol"; import {HashLib} from "../src/ratifiers/libraries/HashLib.sol"; // Paste from frontend output. -address constant ACCOUNT = 0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A; +address constant ACCOUNT = 0xFDa6883171208B36122229505FB2D6F30c052311; uint8 constant SIG_V = 28; -bytes32 constant SIG_R = 0x3b634e6e609860ff1d80ec02a97d6d82bfe7ff35a8108120138ff561460d7040; -bytes32 constant SIG_S = 0x3eb97018d5ccf0711062df8c70faea0971c4f8e9556d57673a03246728bd91c6; +bytes32 constant SIG_R = 0xb7a8c34b3aa87d799f2bd6e01a36c6a48673313015c592fc4137043b37ee80c6; +bytes32 constant SIG_S = 0x5161ce684b17e81a0b297441856e8d8b498c541116fd6dde5d87e5624b847afb; address constant RATIFIER = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB; diff --git a/test/HashLibTest.sol b/test/HashLibTest.sol index a72b62a51..42f5b5e9b 100644 --- a/test/HashLibTest.sol +++ b/test/HashLibTest.sol @@ -14,7 +14,7 @@ bytes constant COLLATERAL_PARAMS_TYPE = "CollateralParams(address token,uint256 bytes constant MARKET_TYPE = "Market(address loanToken,CollateralParams[] collateralParams,uint256 maturity,uint256 rcfThreshold,address enterGate,address liquidatorGate)"; bytes constant OFFER_TYPE = - "Offer(Market market,bool buy,address maker,uint256 start,uint256 expiry,uint256 tick,bytes32 group,address callback,bytes callbackData,address receiverIfMakerIsSeller,address ratifier,bool reduceOnly,uint256 maxUnits,uint256 maxAssets)"; + "Offer(Market market,bool buy,address maker,uint256 start,uint256 expiry,uint256 tick,bytes32 group,address callback,bytes callbackData,address receiverIfMakerIsSeller,address ratifier,bool reduceOnly,uint256 maxUnits,uint256 maxAssets,uint256 continuousFeeCap)"; contract HashLibTest is Test { function testCollateralParamsTypeHash() public pure { diff --git a/test/LiquidationTest.sol b/test/LiquidationTest.sol index e2800d706..cb95826ff 100644 --- a/test/LiquidationTest.sol +++ b/test/LiquidationTest.sol @@ -579,6 +579,24 @@ contract LiquidationTest is BaseTest { assertLe(remainingDebt, newMaxDebt + 3, "position should be approximately just healthy after max repayment"); } + function testLiquidateAccumulatesWithdrawable(uint256 units, uint256 repaid) public { + units = bound(units, 100, MAX_UNITS); + collateralize(market, borrower, units); + setupMarket(market, units); + vm.warp(market.maturity + TIME_TO_MAX_LIF); // post-maturity: liquidatable with no recovery close factor cap. + Oracle(market.collateralParams[0].oracle).setPrice(ORACLE_PRICE_SCALE); + + repaid = bound(repaid, 1, units / 8); // two equal repays stay within debt and collateral capacity. + + assertEq(midnight.withdrawable(id), 0, "withdrawable before"); + + midnight.liquidate(market, 0, 0, repaid, borrower, true, address(this), address(0), ""); + assertEq(midnight.withdrawable(id), repaid, "withdrawable after first liquidation"); + + midnight.liquidate(market, 0, 0, repaid, borrower, true, address(this), address(0), ""); + assertEq(midnight.withdrawable(id), 2 * repaid, "withdrawable after second liquidation"); + } + /// @dev When rcfThreshold > remaining debt after max repayment, full liquidation is allowed pre-maturity. function testRcfThresholdAllowsFullLiquidation(uint256 units, uint256 liquidationOraclePrice, uint256 rcfThreshold) public diff --git a/test/OtherFunctionsTest.sol b/test/OtherFunctionsTest.sol index 3d2274a50..03660e36a 100644 --- a/test/OtherFunctionsTest.sol +++ b/test/OtherFunctionsTest.sol @@ -194,6 +194,45 @@ contract OtherFunctionsTest is BaseTest { assertEq(loanToken.balanceOf(receiver), withdraw, "balance of receiver"); } + function testRepayAccumulatesWithdrawable(uint256 units, uint256 repaid) public { + units = bound(units, 2, MAX_UNITS); + repaid = bound(repaid, 1, units / 2); // two equal repays stay within debt. + collateralize(market, borrower, units); + setupMarket(market, units); + deal(address(loanToken), borrower, 2 * repaid); + + assertEq(midnight.withdrawable(id), 0, "withdrawable before"); + + vm.prank(borrower); + midnight.repay(market, repaid, borrower, address(0), hex""); + assertEq(midnight.withdrawable(id), repaid, "withdrawable after first repay"); + + vm.prank(borrower); + midnight.repay(market, repaid, borrower, address(0), hex""); + assertEq(midnight.withdrawable(id), 2 * repaid, "withdrawable after second repay"); + } + + function testWithdrawDecrementsWithdrawable(uint256 units, uint256 withdrawn) public { + units = bound(units, 2, MAX_UNITS); + withdrawn = bound(withdrawn, 1, units / 2); // two equal withdraws stay within credit. + collateralize(market, borrower, units); + setupMarket(market, units); + + // Fully repay so `units` is withdrawable. + deal(address(loanToken), borrower, units); + vm.prank(borrower); + midnight.repay(market, units, borrower, address(0), hex""); + assertEq(midnight.withdrawable(id), units, "withdrawable before"); + + vm.prank(lender); + midnight.withdraw(market, withdrawn, lender, lender); + assertEq(midnight.withdrawable(id), units - withdrawn, "withdrawable after first withdraw"); + + vm.prank(lender); + midnight.withdraw(market, withdrawn, lender, lender); + assertEq(midnight.withdrawable(id), units - 2 * withdrawn, "withdrawable after second withdraw"); + } + function testWithdrawCollateralToReceiver(uint256 supply, uint256 withdraw) public { supply = bound(supply, 1, MAX_UNITS); withdraw = bound(withdraw, 1, supply); diff --git a/test/SettersTest.sol b/test/SettersTest.sol index 29910f956..f6e59c6d2 100644 --- a/test/SettersTest.sol +++ b/test/SettersTest.sol @@ -162,7 +162,7 @@ contract SettersTest is BaseTest { function testSetMarketSettlementFeeValueTooHigh(bytes32 id, uint256 feeTooHigh, uint256 index) public { index = bound(index, 0, 6); feeTooHigh = bound(feeTooHigh, maxSettlementFee(index) + 1, 1e18); - vm.expectRevert(IMidnight.SettlementFeeTooHigh.selector); + vm.expectRevert(IMidnight.SettlementFeeAboveMax.selector); midnight.setMarketSettlementFee(id, index, feeTooHigh); } @@ -305,7 +305,7 @@ contract SettersTest is BaseTest { function testSetDefaultSettlementFeeValidation(address loanToken, uint256 feeTooHigh, uint256 index) public { index = bound(index, 0, 6); feeTooHigh = bound(feeTooHigh, maxSettlementFee(index) + 1, 1e18); - vm.expectRevert(IMidnight.SettlementFeeTooHigh.selector); + vm.expectRevert(IMidnight.SettlementFeeAboveMax.selector); midnight.setDefaultSettlementFee(loanToken, index, feeTooHigh); } @@ -422,10 +422,10 @@ contract SettersTest is BaseTest { midnight.touchMarket(market); bytes32 id = toId(market); - vm.expectRevert(IMidnight.ContinuousFeeTooHigh.selector); + vm.expectRevert(IMidnight.ContinuousFeeAboveMax.selector); midnight.setMarketContinuousFee(id, fee); - vm.expectRevert(IMidnight.ContinuousFeeTooHigh.selector); + vm.expectRevert(IMidnight.ContinuousFeeAboveMax.selector); midnight.setDefaultContinuousFee(address(loanToken), fee); } diff --git a/test/TakeAmountsTest.sol b/test/TakeAmountsTest.sol index 22886ee40..d69a8b1b1 100644 --- a/test/TakeAmountsTest.sol +++ b/test/TakeAmountsTest.sol @@ -12,6 +12,8 @@ import {TakeAmountsLib} from "../src/periphery/TakeAmountsLib.sol"; contract TakeAmountsTest is BaseTest { using UtilsLib for uint256; + uint256 internal constant BORROWER_POSITION_UNITS = 1e37; + Market internal market; bytes32 internal id; Offer internal offer; @@ -144,7 +146,7 @@ contract TakeAmountsTest is BaseTest { targetBuyerAssets = bound(targetBuyerAssets, 1, 1e30); tick = bound(tick, 4, _maxTick(settlementFee) / DEFAULT_TICK_SPACING) * DEFAULT_TICK_SPACING; - _createPosition(1e36); + _createPosition(BORROWER_POSITION_UNITS); offer.maker = lender; offer.receiverIfMakerIsSeller = lender; @@ -167,7 +169,7 @@ contract TakeAmountsTest is BaseTest { targetSellerAssets = bound(targetSellerAssets, 1, 1e30); tick = bound(tick, 4, _maxTick(settlementFee) / DEFAULT_TICK_SPACING) * DEFAULT_TICK_SPACING; - _createPosition(1e36); + _createPosition(BORROWER_POSITION_UNITS); offer.maker = lender; offer.receiverIfMakerIsSeller = lender; @@ -212,7 +214,7 @@ contract TakeAmountsTest is BaseTest { uint256 settlementFee = _setSettlementFees(settlementFee0, settlementFee1); targetBuyerAssets = bound(targetBuyerAssets, 1, 1e30); - _createPosition(1e36); + _createPosition(BORROWER_POSITION_UNITS); uint256 buyerPrice = TickLib.tickToPrice(MAX_TICK) + settlementFee; uint256 targetUnits = targetBuyerAssets.mulDivUp(WAD, buyerPrice); diff --git a/test/TakeTest.sol b/test/TakeTest.sol index 86ac05415..e8c820b0b 100644 --- a/test/TakeTest.sol +++ b/test/TakeTest.sol @@ -64,6 +64,7 @@ contract TakeTest is BaseTest { lenderOffer.maker = lender; lenderOffer.ratifier = address(dummyRatifier); lenderOffer.maxUnits = type(uint256).max; + lenderOffer.continuousFeeCap = type(uint256).max; lenderOffer.market = market; lenderOffer.expiry = vm.getBlockTimestamp() + 200; lenderOffer.tick = MAX_TICK; @@ -73,6 +74,7 @@ contract TakeTest is BaseTest { otherLenderOffer.ratifier = address(dummyRatifier); otherLenderOffer.receiverIfMakerIsSeller = otherLender; otherLenderOffer.maxUnits = type(uint256).max; + otherLenderOffer.continuousFeeCap = type(uint256).max; otherLenderOffer.market = market; otherLenderOffer.expiry = vm.getBlockTimestamp() + 200; otherLenderOffer.tick = MAX_TICK; @@ -82,6 +84,7 @@ contract TakeTest is BaseTest { borrowerOffer.ratifier = address(dummyRatifier); borrowerOffer.receiverIfMakerIsSeller = borrower; borrowerOffer.maxUnits = type(uint256).max; + borrowerOffer.continuousFeeCap = type(uint256).max; borrowerOffer.market = market; borrowerOffer.expiry = vm.getBlockTimestamp() + 200; borrowerOffer.tick = MAX_TICK; @@ -90,6 +93,7 @@ contract TakeTest is BaseTest { otherBorrowerOffer.maker = otherBorrower; otherBorrowerOffer.ratifier = address(dummyRatifier); otherBorrowerOffer.maxUnits = type(uint256).max; + otherBorrowerOffer.continuousFeeCap = type(uint256).max; otherBorrowerOffer.market = market; otherBorrowerOffer.expiry = vm.getBlockTimestamp() + 200; otherBorrowerOffer.tick = MAX_TICK; @@ -125,6 +129,7 @@ contract TakeTest is BaseTest { collateralize(market, lender, existingDebt); Offer memory buy0 = _setupMarketOffer(market, existingDebt); buy0.buy = true; + buy0.continuousFeeCap = type(uint256).max; buy0.maker = otherLender; buy0.receiverIfMakerIsSeller = address(0); // forge-lint: disable-next-line(unsafe-typecast) @@ -937,6 +942,55 @@ contract TakeTest is BaseTest { assertEq(buyerAssets, expectedBuyerAssets); } + function testMaxAssetsSellerConsumedAccumulates(uint256 units) public { + units = bound(units, 1, maxAssets / 2); + deal(address(loanToken), lender, type(uint128).max); + collateralize(market, borrower, 2 * units); + + borrowerOffer.maxUnits = 0; + borrowerOffer.maxAssets = type(uint128).max; + + (, uint256 sellerAssets1) = take(units, lender, borrowerOffer); + assertEq(midnight.consumed(borrower, borrowerOffer.group), sellerAssets1, "consumed after first take"); + + (, uint256 sellerAssets2) = take(units, lender, borrowerOffer); + assertEq( + midnight.consumed(borrower, borrowerOffer.group), + sellerAssets1 + sellerAssets2, + "consumed after second take" + ); + } + + function testMaxAssetsBuyerConsumedAccumulates(uint256 units) public { + units = bound(units, 1, maxAssets / 2); + deal(address(loanToken), lender, type(uint128).max); + collateralize(market, borrower, 2 * units); + + lenderOffer.maxUnits = 0; + lenderOffer.maxAssets = type(uint128).max; + + (uint256 buyerAssets1,) = take(units, borrower, lenderOffer); + assertEq(midnight.consumed(lender, lenderOffer.group), buyerAssets1, "consumed after first take"); + + (uint256 buyerAssets2,) = take(units, borrower, lenderOffer); + assertEq( + midnight.consumed(lender, lenderOffer.group), buyerAssets1 + buyerAssets2, "consumed after second take" + ); + } + + function testMaxUnitsConsumedAccumulates(uint256 units) public { + units = bound(units, 1, maxAssets / 2); + deal(address(loanToken), lender, type(uint128).max); + collateralize(market, borrower, 2 * units); + + // Default offer caps: maxUnits set, maxAssets == 0 -> consumed tracks units. + take(units, lender, borrowerOffer); + assertEq(midnight.consumed(borrower, borrowerOffer.group), units, "consumed after first take"); + + take(units, lender, borrowerOffer); + assertEq(midnight.consumed(borrower, borrowerOffer.group), 2 * units, "consumed after second take"); + } + function testMaxAssetsZeroMeansNoLimitForSeller(uint256 units) public { units = bound(units, 1, maxAssets); deal(address(loanToken), lender, units); @@ -1063,7 +1117,7 @@ contract TakeTest is BaseTest { vm.prank(maker); midnight.setIsAuthorized(address(ratifier), true, maker); - vm.expectRevert(IMidnight.RatifierFail.selector); + vm.expectRevert(IMidnight.RatifierFailed.selector); vm.prank(sender); midnight.take(lenderOffer, hex"", 0, sender, sender, address(0), hex""); } diff --git a/test/TickLibTest.sol b/test/TickLibTest.sol index f19d71451..1f839741c 100644 --- a/test/TickLibTest.sol +++ b/test/TickLibTest.sol @@ -5,17 +5,22 @@ import {BaseTest} from "./BaseTest.sol"; import {console} from "forge-std/Test.sol"; import {TickLib} from "../src/libraries/TickLib.sol"; import {UtilsLib} from "../src/libraries/UtilsLib.sol"; -import {MAX_TICK} from "../src/libraries/TickLib.sol"; +import {MAX_TICK, PRICE_ROUNDING_STEP} from "../src/libraries/TickLib.sol"; contract TickLibTest is BaseTest { using UtilsLib for uint256; // Tick to price + function testPriceRoundingStep() public pure { + assertEq(PRICE_ROUNDING_STEP, 0.0000001e18); + } + function testTickToPriceMinMax() public pure { + assertEq(MAX_TICK, 6744, "max tick"); assertEq(TickLib.tickToPrice(0), 0, "tick 0"); - assertEq(TickLib.tickToPrice(2), 1e12, "first non-zero tick"); - assertEq(TickLib.tickToPrice(MAX_TICK - 2), 1e18 - 1e12, "tick max - 2 just below par"); + assertEq(TickLib.tickToPrice(2), 0.0000001e18, "first non-zero tick"); + assertEq(TickLib.tickToPrice(MAX_TICK - 2), 1e18 - 0.0000001e18, "tick max - 2 just below par"); assertEq(TickLib.tickToPrice(MAX_TICK), 1e18, "tick max"); } @@ -129,8 +134,8 @@ contract TickLibTest is BaseTest { totalRelErrorWad += relErrorWad; maxRelErrorWad = max(maxRelErrorWad, relErrorWad); - // 3-term Taylor in wExp yields max ~1.4 bps absolute error; 2 bps threshold leaves headroom. - assertLe(absErrorWad, 0.00014e18, string.concat("Tick ", vm.toString(tick), " error exceeds 2 bps")); + // 3-term Taylor in wExp yields sub-2 bps absolute error. + assertLe(absErrorWad, 0.0002e18, string.concat("Tick ", vm.toString(tick), " error exceeds 2 bps")); if (solPrice > 0.01e18) { assertLe(relErrorWad, 0.0007e18, string.concat("Tick ", vm.toString(tick), " error exceeds 7 bps")); } diff --git a/test/frontend/sign-root.ts b/test/frontend/sign-root.ts index acc7bac69..5235bed28 100644 --- a/test/frontend/sign-root.ts +++ b/test/frontend/sign-root.ts @@ -54,6 +54,7 @@ function buildTypes(height: number) { { name: "reduceOnly", type: "bool" }, { name: "maxUnits", type: "uint256" }, { name: "maxAssets", type: "uint256" }, + { name: "continuousFeeCap", type: "uint256" }, ], }; } @@ -81,6 +82,7 @@ function defaultOffer(number: string) { reduceOnly: false, maxUnits: "0", maxAssets: "0", + continuousFeeCap: "0", }; } diff --git a/test/ticks_exact.json b/test/ticks_exact.json index ffbf46848..a81e0802b 100644 --- a/test/ticks_exact.json +++ b/test/ticks_exact.json @@ -1,5 +1,467 @@ { "prices": [ + "49663775891", + "49912094758", + "50161655219", + "50412463483", + "50664525788", + "50917848404", + "51172437633", + "51428299808", + "51685441293", + "51943868486", + "52203587815", + "52464605741", + "52726928756", + "52990563385", + "53255516188", + "53521793755", + "53789402709", + "54058349708", + "54328641442", + "54600284634", + "54873286043", + "55147652458", + "55423390705", + "55700507643", + "55979010166", + "56258905201", + "56540199711", + "56822900693", + "57107015180", + "57392550240", + "57679512975", + "57967910523", + "58257750058", + "58549038792", + "58841783968", + "59135992871", + "59431672818", + "59728831164", + "60027475302", + "60327612660", + "60629250705", + "60932396940", + "61237058906", + "61543244182", + "61850960384", + "62160215167", + "62471016223", + "62783371285", + "63097288121", + "63412774542", + "63729838394", + "64048487566", + "64368729983", + "64690573612", + "65014026459", + "65339096570", + "65665792032", + "65994120970", + "66324091553", + "66655711989", + "66988990527", + "67323935457", + "67660555111", + "67998857864", + "68338852130", + "68680546367", + "69023949075", + "69369068796", + "69715914116", + "70064493662", + "70414816106", + "70766890162", + "71120724587", + "71476328185", + "71833709800", + "72192878323", + "72553842689", + "72916611876", + "73281194908", + "73647600856", + "74015838833", + "74385918000", + "74757847562", + "75131636771", + "75507294927", + "75884831373", + "76264255501", + "76645576749", + "77028804603", + "77413948597", + "77801018309", + "78190023371", + "78580973457", + "78973878293", + "79368747653", + "79765591360", + "80164419285", + "80565241349", + "80968067523", + "81372907827", + "81779772333", + "82188671161", + "82599614483", + "83012612521", + "83427675549", + "83844813892", + "84264037926", + "84685358080", + "85108784835", + "85534328722", + "85962000329", + "86391810294", + "86823769308", + "87257888116", + "87694177519", + "88132648368", + "88573311570", + "89016178089", + "89461258940", + "89908565194", + "90358107979", + "90809898478", + "91263947929", + "91720267627", + "92178868923", + "92639763225", + "93102961998", + "93568476764", + "94036319104", + "94506500655", + "94979033114", + "95453928234", + "95931197829", + "96410853772", + "96892907994", + "97377372487", + "97864259302", + "98353580550", + "98845348404", + "99339575097", + "99836272923", + "100335454238", + "100837131458", + "101341317065", + "101848023598", + "102357263664", + "102869049930", + "103383395126", + "103900312048", + "104419813554", + "104941912567", + "105466622075", + "105993955129", + "106523924848", + "107056544416", + "107591827080", + "108129786157", + "108670435029", + "109213787145", + "109759856021", + "110308655240", + "110860198456", + "111414499386", + "111971571821", + "112531429617", + "113094086701", + "113659557070", + "114227854791", + "114798993999", + "115372988903", + "115949853781", + "116529602982", + "117112250929", + "117697812114", + "118286301105", + "118877732541", + "119472121132", + "120069481666", + "120669829002", + "121273178074", + "121879543890", + "122488941535", + "123101386168", + "123716893022", + "124335477410", + "124957154720", + "125581940415", + "126209850038", + "126840899208", + "127475103623", + "128112479060", + "128753041372", + "129396806496", + "130043790444", + "130694009312", + "131347479272", + "132004216582", + "132664237577", + "133327558677", + "133994196381", + "134664167272", + "135337488018", + "136014175366", + "136694246150", + "137377717286", + "138064605778", + "138754928711", + "139448703258", + "140145946677", + "140846676311", + "141550909593", + "142258664040", + "142969957259", + "143684806943", + "144403230874", + "145125246923", + "145850873052", + "146580127310", + "147313027839", + "148049592869", + "148789840723", + "149533789816", + "150281458652", + "151032865832", + "151788030047", + "152546970081", + "153309704815", + "154076253220", + "154846634367", + "155620867419", + "156398971634", + "157180966369", + "157966871077", + "158756705307", + "159550488707", + "160348241023", + "161149982098", + "161955731878", + "162765510406", + "163579337825", + "164397234380", + "165219220416", + "166045316381", + "166875542824", + "167709920398", + "168548469859", + "169391212065", + "170238167982", + "171089358676", + "171944805322", + "172804529200", + "173668551696", + "174536894303", + "175409578621", + "176286626360", + "177168059336", + "178053899475", + "178944168813", + "179838889496", + "180738083781", + "181641774035", + "182549982740", + "183462732486", + "184380045979", + "185301946038", + "186228455596", + "187159597700", + "188095395512", + "189035872312", + "189981051494", + "190930956570", + "191885611170", + "192845039041", + "193809264049", + "194778310181", + "195752201541", + "196730962356", + "197714616973", + "198703189862", + "199696705613", + "200695188940", + "201698664683", + "202707157802", + "203720693384", + "204739296642", + "205762992915", + "206791807667", + "207825766490", + "208864895106", + "209909219362", + "210958765237", + "212013558840", + "213073626408", + "214138994312", + "215209689053", + "216285737266", + "217367165717", + "218454001308", + "219546271075", + "220644002188", + "221747221955", + "222855957817", + "223970237357", + "225090088292", + "226215538478", + "227346615914", + "228483348733", + "229625765215", + "230773893776", + "231927762977", + "233087401522", + "234252838256", + "235424102172", + "236601222404", + "237784228235", + "238973149092", + "240168014551", + "241368854333", + "242575698312", + "243788576508", + "245007519092", + "246232556386", + "247463718863", + "248701037150", + "249944542025", + "251194264421", + "252450235426", + "253712486283", + "254981048391", + "256255953306", + "257537232743", + "258824918573", + "260119042829", + "261419637704", + "262726735549", + "264040368880", + "265360570374", + "266687372872", + "268020809379", + "269360913064", + "270707717265", + "272061255483", + "273421561389", + "274788668820", + "276162611785", + "277543424460", + "278931141196", + "280325796511", + "281727425098", + "283136061825", + "284551741731", + "285974500033", + "287404372122", + "288841393568", + "290285600116", + "291737027694", + "293195712404", + "294661690534", + "296134998551", + "297615673103", + "299103751023", + "300599269329", + "302102265222", + "303612776089", + "305130839506", + "306656493236", + "308189775230", + "309730723628", + "311279376765", + "312835773161", + "314399951535", + "315971950796", + "317551810049", + "319139568592", + "320735265923", + "322338941736", + "323950635923", + "325570388575", + "327198239985", + "328834230647", + "330478401257", + "332130792715", + "333791446124", + "335460402795", + "337137704243", + "338823392193", + "340517508577", + "342220095537", + "343931195427", + "345650850809", + "347379104463", + "349115999379", + "350861578763", + "352615886039", + "354378964844", + "356150859037", + "357931612695", + "359721270115", + "361519875815", + "363327474537", + "365144111247", + "366969831133", + "368804679612", + "370648702327", + "372501945148", + "374364454176", + "376236275743", + "378117456410", + "380008042974", + "381908082463", + "383817622143", + "385736709513", + "387665392313", + "389603718519", + "391551736349", + "393509494261", + "395477040954", + "397454425373", + "399441696706", + "401438904387", + "403446098100", + "405463327772", + "407490643585", + "409528095968", + "411575735606", + "413633613432", + "415701780640", + "417780288675", + "419869189241", + "421968534301", + "424078376078", + "426198767055", + "428329759977", + "430471407855", + "432623763963", + "434786881843", + "436960815302", + "439145618419", + "441341345542", + "443548051291", + "445765790559", + "447994618513", + "450234590597", + "452485762532", + "454748190315", + "457021930228", + "459307038829", + "461603572963", + "463911589758", + "466231146625", + "468562301266", + "470905111669", + "473259636113", + "475625933168", + "478004061697", + "480394080857", + "482796050102", + "485210029181", + "487636078144", + "490074257340", + "492524627420", + "494987249338", "497462184353", "499949494032", "502449240246", @@ -5820,6 +6282,468 @@ "999999495038514816", "999999497550759808", "999999500050505856", - "999999502537815552" + "999999502537815552", + "999999505012750720", + "999999507475372544", + "999999509925742720", + "999999512363921792", + "999999514789970816", + "999999517203949952", + "999999519605919104", + "999999521995938304", + "999999524374066816", + "999999526740363776", + "999999529094888320", + "999999531437698816", + "999999533768853376", + "999999536088410240", + "999999538396427008", + "999999540692961152", + "999999542978069888", + "999999545251809664", + "999999547514237440", + "999999549765409536", + "999999552005381504", + "999999554234209536", + "999999556451948544", + "999999558658654336", + "999999560854381568", + "999999563039184640", + "999999565213118208", + "999999567376236032", + "999999569528592000", + "999999571670240000", + "999999573801232896", + "999999575921624064", + "999999578031465600", + "999999580130810752", + "999999582219711488", + "999999584298219264", + "999999586366386560", + "999999588424264448", + "999999590471904000", + "999999592509356544", + "999999594536672256", + "999999596553901952", + "999999598561095552", + "999999600558303232", + "999999602545574784", + "999999604522958976", + "999999606490505728", + "999999608448263680", + "999999610396281472", + "999999612334607744", + "999999614263290624", + "999999616182377728", + "999999618091917440", + "999999619991956992", + "999999621882543488", + "999999623763724160", + "999999625635545728", + "999999627498054912", + "999999629351297792", + "999999631195320448", + "999999633030168960", + "999999634855888768", + "999999636672525568", + "999999638480124160", + "999999640278729856", + "999999642068387200", + "999999643849140992", + "999999645621035008", + "999999647384113920", + "999999649138421248", + "999999650884000512", + "999999652620895488", + "999999654349149184", + "999999656068804608", + "999999657779904512", + "999999659482491392", + "999999661176607744", + "999999662862295808", + "999999664539597184", + "999999666208553856", + "999999667869207296", + "999999669521598720", + "999999671165769216", + "999999672801760000", + "999999674429611392", + "999999676049364096", + "999999677661058176", + "999999679264734208", + "999999680860431360", + "999999682448189824", + "999999684028049152", + "999999685600048384", + "999999687164226944", + "999999688720623104", + "999999690269276288", + "999999691810224640", + "999999693343506816", + "999999694869160576", + "999999696387224064", + "999999697897734784", + "999999699400730752", + "999999700896248832", + "999999702384327040", + "999999703865001472", + "999999705338309376", + "999999706804287616", + "999999708262972160", + "999999709714399872", + "999999711158606464", + "999999712595627904", + "999999714025500032", + "999999715448258176", + "999999716863938048", + "999999718272574976", + "999999719674203392", + "999999721068858752", + "999999722456575488", + "999999723837388160", + "999999725211331072", + "999999726578438528", + "999999727938744576", + "999999729292282624", + "999999730639086976", + "999999731979190528", + "999999733312627200", + "999999734639429632", + "999999735959631104", + "999999737273264512", + "999999738580362240", + "999999739880957184", + "999999741175081344", + "999999742462767232", + "999999743744046592", + "999999745018951680", + "999999746287513600", + "999999747549764608", + "999999748805735552", + "999999750055457920", + "999999751298962816", + "999999752536281088", + "999999753767443712", + "999999754992480896", + "999999756211423488", + "999999757424301568", + "999999758631145600", + "999999759831985408", + "999999761026850944", + "999999762215771776", + "999999763398777600", + "999999764575897984", + "999999765747161728", + "999999766912598528", + "999999768072237056", + "999999769226106240", + "999999770374234752", + "999999771516651392", + "999999772653384192", + "999999773784461568", + "999999774909911680", + "999999776029762688", + "999999777144042240", + "999999778252777984", + "999999779355997824", + "999999780453728896", + "999999781545998592", + "999999782632834304", + "999999783714262784", + "999999784790310912", + "999999785861005568", + "999999786926373632", + "999999787986441216", + "999999789041234688", + "999999790090780544", + "999999791135104896", + "999999792174233472", + "999999793208192256", + "999999794237007104", + "999999795260703360", + "999999796279306496", + "999999797292842240", + "999999798301335424", + "999999799304811008", + "999999800303294336", + "999999801296810112", + "999999802285383168", + "999999803269037696", + "999999804247798528", + "999999805221689856", + "999999806190735872", + "999999807154961024", + "999999808114388736", + "999999809069043456", + "999999810018948608", + "999999810964127744", + "999999811904604544", + "999999812840402304", + "999999813771544448", + "999999814698053888", + "999999815619954176", + "999999816537267584", + "999999817450017280", + "999999818358226048", + "999999819261916160", + "999999820161110528", + "999999821055831168", + "999999821946100608", + "999999822831940736", + "999999823713373568", + "999999824590421504", + "999999825463105792", + "999999826331448192", + "999999827195470720", + "999999828055194752", + "999999828910641280", + "999999829761832064", + "999999830608787840", + "999999831451530112", + "999999832290079488", + "999999833124457216", + "999999833954683648", + "999999834780779648", + "999999835602765696", + "999999836420662272", + "999999837234489728", + "999999838044268032", + "999999838850017920", + "999999839651759104", + "999999840449511296", + "999999841243294720", + "999999842033128960", + "999999842819033600", + "999999843601028224", + "999999844379132544", + "999999845153365632", + "999999845923746688", + "999999846690295296", + "999999847453030016", + "999999848211970048", + "999999848967134208", + "999999849718541312", + "999999850466210176", + "999999851210159232", + "999999851950407168", + "999999852686972160", + "999999853419872768", + "999999854149127040", + "999999854874753024", + "999999855596769024", + "999999856315193088", + "999999857030042752", + "999999857741335808", + "999999858449090432", + "999999859153323776", + "999999859854053248", + "999999860551296640", + "999999861245071232", + "999999861935394176", + "999999862622282752", + "999999863305753856", + "999999863985824640", + "999999864662512000", + "999999865335832832", + "999999866005803520", + "999999866672441216", + "999999867335762432", + "999999867995783424", + "999999868652520832", + "999999869305990528", + "999999869956209664", + "999999870603193472", + "999999871246958592", + "999999871887520896", + "999999872524896512", + "999999873159100800", + "999999873790150016", + "999999874418059648", + "999999875042845184", + "999999875664522624", + "999999876283107072", + "999999876898613888", + "999999877511058432", + "999999878120456192", + "999999878726821888", + "999999879330170880", + "999999879930518272", + "999999880527879040", + "999999881122267392", + "999999881713698816", + "999999882302187904", + "999999882887749120", + "999999883470396928", + "999999884050146176", + "999999884627011072", + "999999885201005952", + "999999885772145280", + "999999886340442880", + "999999886905913344", + "999999887468570240", + "999999888028428032", + "999999888585500544", + "999999889139801472", + "999999889691344896", + "999999890240144000", + "999999890786212864", + "999999891329564928", + "999999891870213888", + "999999892408173056", + "999999892943455616", + "999999893476075264", + "999999894006044928", + "999999894533377920", + "999999895058087424", + "999999895580186368", + "999999896099688064", + "999999896616604800", + "999999897130950016", + "999999897642736256", + "999999898151976320", + "999999898658682880", + "999999899162868608", + "999999899664545664", + "999999900163727232", + "999999900660424832", + "999999901154651648", + "999999901646419456", + "999999902135740672", + "999999902622627584", + "999999903107091968", + "999999903589146240", + "999999904068802176", + "999999904546071680", + "999999905020966784", + "999999905493499392", + "999999905963680768", + "999999906431523072", + "999999906897037952", + "999999907360236672", + "999999907821131136", + "999999908279732352", + "999999908736052096", + "999999909190101504", + "999999909641892096", + "999999910091434880", + "999999910538741120", + "999999910983821952", + "999999911426688512", + "999999911867351680", + "999999912305822464", + "999999912742111872", + "999999913176230656", + "999999913608189824", + "999999914037999616", + "999999914465671296", + "999999914891215232", + "999999915314642048", + "999999915735961984", + "999999916155186176", + "999999916572324480", + "999999916987387392", + "999999917400385408", + "999999917811328896", + "999999918220227712", + "999999918627092224", + "999999919031932544", + "999999919434758656", + "999999919835580672", + "999999920234408704", + "999999920631252224", + "999999921026121728", + "999999921419026432", + "999999921809976576", + "999999922198981760", + "999999922586051456", + "999999922971195520", + "999999923354423296", + "999999923735744512", + "999999924115168640", + "999999924492705024", + "999999924868363136", + "999999925242152320", + "999999925614081920", + "999999925984161152", + "999999926352399232", + "999999926718805120", + "999999927083388160", + "999999927446157184", + "999999927807121792", + "999999928166290176", + "999999928523671808", + "999999928879275264", + "999999929233109760", + "999999929585183872", + "999999929935506304", + "999999930284085760", + "999999930630931200", + "999999930976050816", + "999999931319453696", + "999999931661147904", + "999999932001142144", + "999999932339444992", + "999999932676064512", + "999999933011009408", + "999999933344288000", + "999999933675908352", + "999999934005879040", + "999999934334208000", + "999999934660903424", + "999999934985973632", + "999999935309426432", + "999999935631270016", + "999999935951512448", + "999999936270161536", + "999999936587225472", + "999999936902711936", + "999999937216628736", + "999999937528983808", + "999999937839784832", + "999999938149039744", + "999999938456755712", + "999999938762941056", + "999999939067603072", + "999999939370749440", + "999999939672387456", + "999999939972524672", + "999999940271168768", + "999999940568327296", + "999999940864007168", + "999999941158216064", + "999999941450961152", + "999999941742249856", + "999999942032089600", + "999999942320487040", + "999999942607449728", + "999999942892984832", + "999999943177099264", + "999999943459800192", + "999999943741094784", + "999999944020989952", + "999999944299492480", + "999999944576609152", + "999999944852347520", + "999999945126713984", + "999999945399715328", + "999999945671358464", + "999999945941650176", + "999999946210597248", + "999999946478206336", + "999999946744483712", + "999999947009436672", + "999999947273071232", + "999999947535394304", + "999999947796412160", + "999999948056131456", + "999999948314558720", + "999999948571700096", + "999999948827562368", + "999999949082151552", + "999999949335474176", + "999999949587536512", + "999999949838344832", + "999999950087905152", + "999999950336224000" ] } \ No newline at end of file diff --git a/test/ticks_exact_gen.py b/test/ticks_exact_gen.py index ed36710e3..c6f4bcec0 100644 --- a/test/ticks_exact_gen.py +++ b/test/ticks_exact_gen.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import json -MAX_TICK = 5820 +MAX_TICK = 6744 def exact_price(tick): """Reference tick to price implementation.""" From b2745c898a92f1535edeee2938e9681e048ee16d Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Mon, 22 Jun 2026 15:27:33 +0200 Subject: [PATCH 13/18] fix merge --- test/SetIsAuthorizedWithSigTest.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/SetIsAuthorizedWithSigTest.sol b/test/SetIsAuthorizedWithSigTest.sol index 0634f12cc..33adb84d1 100644 --- a/test/SetIsAuthorizedWithSigTest.sol +++ b/test/SetIsAuthorizedWithSigTest.sol @@ -67,7 +67,7 @@ contract EcrecoverAuthorizerTest is BaseTest { Signature memory sig = signAuthorization(auth, borrower); vm.expectEmit(); - emit IEcrecoverAuthorizer.SetIsAuthorized(address(this), borrower, lender, true, auth.nonce); + emit IEcrecoverAuthorizer.SetIsAuthorized(address(this), borrower, lender, true, auth.nonce, borrower); ecrecoverAuthorizer.setIsAuthorized(auth, sig); @@ -78,7 +78,7 @@ contract EcrecoverAuthorizerTest is BaseTest { sig = signAuthorization(auth, borrower); vm.expectEmit(); - emit IEcrecoverAuthorizer.SetIsAuthorized(address(this), borrower, lender, false, auth.nonce); + emit IEcrecoverAuthorizer.SetIsAuthorized(address(this), borrower, lender, false, auth.nonce, borrower); ecrecoverAuthorizer.setIsAuthorized(auth, sig); From 91c4f3c1fa4258cc36a2271892c6779a9561be7a Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Mon, 22 Jun 2026 16:10:10 +0200 Subject: [PATCH 14/18] go back to a single event, log entire offer --- src/Midnight.sol | 15 +------- src/libraries/EventsLib.sol | 5 ++- test/ContinuousFeeTest.sol | 15 +------- test/TakeEventGasTest.sol | 71 +++++++++++++++++++++++++++++++++++++ test/TakeTest.sol | 15 +------- 5 files changed, 76 insertions(+), 45 deletions(-) create mode 100644 test/TakeEventGasTest.sol diff --git a/src/Midnight.sol b/src/Midnight.sol index ca185e230..846c44e92 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -451,6 +451,7 @@ contract Midnight is IMidnight { id, units, taker, + offer, buyerAssets, sellerAssets, newConsumed, @@ -463,20 +464,6 @@ contract Midnight is IMidnight { takerCallback ); - emit EventsLib.TakenOffer( - offer.maker, - offer.tick, - offer.start, - offer.expiry, - offer.reduceOnly, - offer.maxUnits, - offer.maxAssets, - offer.buy, - offer.group, - offer.callback, - offer.ratifier - ); - bool wasLocked = UtilsLib.tExchange(LIQUIDATION_LOCK_SLOT, id, seller, true); if (buyerCallback != address(0)) { bytes memory buyerCallbackData = offer.buy ? offer.callbackData : takerCallbackData; diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 088ab9ca7..a2af6889c 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -2,7 +2,7 @@ // Copyright (c) 2025 Morpho Association pragma solidity ^0.8.0; -import {Market} from "../interfaces/IMidnight.sol"; +import {Market, Offer} from "../interfaces/IMidnight.sol"; /// @dev id_ is used to avoid naming conflicts in indexers. library EventsLib { @@ -20,8 +20,7 @@ library EventsLib { event SetDefaultContinuousFee(address indexed loanToken, uint256 newContinuousFee); event UpdatePosition(bytes32 indexed id_, address indexed user, uint256 creditDecrease, uint256 pendingFeeDecrease, uint256 accruedFee); event MarketCreated(Market market, bytes32 indexed id_); - event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback); - event TakenOffer(address indexed maker, uint256 tick, uint256 start, uint256 expiry, bool reduceOnly, uint256 maxUnits, uint256 maxAssets, bool buy, bytes32 group, address callback, address ratifier); + event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, Offer offer, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback); event Withdraw(address caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address indexed receiver, uint256 pendingFeeDecrease); event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer); event SupplyCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf); diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index c389386d8..890b3cff9 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -282,6 +282,7 @@ contract ContinuousFeeTest is BaseTest { id, exitAmount, lender, + _makeBuyOffer(keccak256("lender-exit")), takeAssets, takeAssets, exitAmount, @@ -293,20 +294,6 @@ contract ContinuousFeeTest is BaseTest { otherLender, address(0) ); - vm.expectEmit(); - emit EventsLib.TakenOffer( - otherLender, - MAX_TICK, - 0, - vm.getBlockTimestamp(), - false, - type(uint256).max, - 0, - true, - keccak256("lender-exit"), - address(0), - address(dummyRatifier) - ); take(exitAmount, lender, _makeBuyOffer(keccak256("lender-exit"))); // lender is taker = seller uint256 expectedRemaining = creditAfterAccrual > 0 ? remainingAfterAccrual - sellerPendingFeeDecrease : 0; diff --git a/test/TakeEventGasTest.sol b/test/TakeEventGasTest.sol new file mode 100644 index 000000000..5a8380484 --- /dev/null +++ b/test/TakeEventGasTest.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2025 Morpho Association +pragma solidity ^0.8.0; + +import {Market, Offer, CollateralParams} from "../src/interfaces/IMidnight.sol"; +import {MAX_TICK} from "../src/libraries/TickLib.sol"; +import {BaseTest} from "./BaseTest.sol"; + +contract TakeEventGasTest is BaseTest { + Market internal market; + Offer internal storedOffer; + uint256 internal storedUnits; + address internal storedTaker; + address internal storedReceiver; + address internal storedTakerCallback; + bytes internal storedRatifierData; + bytes internal storedTakerCallbackData; + uint256 internal sink; + + function setUp() public override { + super.setUp(); + + market.loanToken = address(loanToken); + market.maturity = vm.getBlockTimestamp() + 100 days; + market.collateralParams + .push( + CollateralParams({ + token: address(collateralToken1), + lltv: 0.77e18, + maxLif: maxLif(0.77e18, 0.25e18), + oracle: address(oracle1) + }) + ); + market.rcfThreshold = 0; + + storedUnits = 1e18; + storedTaker = borrower; + storedReceiver = borrower; + + storedOffer.market = market; + storedOffer.buy = true; + storedOffer.maker = lender; + storedOffer.ratifier = address(dummyRatifier); + storedOffer.maxUnits = type(uint256).max; + storedOffer.continuousFeeCap = type(uint256).max; + storedOffer.expiry = vm.getBlockTimestamp() + 100 days; + storedOffer.tick = MAX_TICK; + + collateralize(market, borrower, storedUnits); + deal(address(loanToken), lender, storedUnits); + } + + function testGasTakeSmall() public { + Offer memory offer = storedOffer; + bytes memory ratifierData = storedRatifierData; + uint256 units = storedUnits; + address taker = storedTaker; + address receiver = storedReceiver; + address takerCallback = storedTakerCallback; + bytes memory takerCallbackData = storedTakerCallbackData; + + vm.prank(taker); + uint256 gasBefore = gasleft(); + (uint256 buyerAssets, uint256 sellerAssets) = + midnight.take(offer, ratifierData, units, taker, receiver, takerCallback, takerCallbackData); + uint256 gasUsed = gasBefore - gasleft(); + + sink = buyerAssets + sellerAssets + gasUsed; + emit log_named_uint("take gas", gasUsed); + } +} diff --git a/test/TakeTest.sol b/test/TakeTest.sol index e719d5808..48926c358 100644 --- a/test/TakeTest.sol +++ b/test/TakeTest.sol @@ -188,6 +188,7 @@ contract TakeTest is BaseTest { id, units, taker, + offer, buyerAssets, sellerAssets, existingConsumed + units, @@ -199,20 +200,6 @@ contract TakeTest is BaseTest { address(payerCallback), offerIsBuy ? address(0) : address(payerCallback) ); - vm.expectEmit(); - emit EventsLib.TakenOffer( - offer.maker, - offer.tick, - offer.start, - offer.expiry, - offer.reduceOnly, - offer.maxUnits, - offer.maxAssets, - offer.buy, - offer.group, - offer.callback, - offer.ratifier - ); vm.prank(caller); if (offerIsBuy) { From 5cd3ef3ec46c11c1db7d474606fc6707c6bb0180 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Mon, 22 Jun 2026 16:56:04 +0200 Subject: [PATCH 15/18] increase vacuity timeout --- certora/confs/Reentrancy.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/certora/confs/Reentrancy.conf b/certora/confs/Reentrancy.conf index 1940cfb8d..a829328e8 100644 --- a/certora/confs/Reentrancy.conf +++ b/certora/confs/Reentrancy.conf @@ -4,7 +4,8 @@ ], "prover_args": [ "-enableStorageSplitting false", - "-destructiveOptimizations twostage" + "-destructiveOptimizations twostage", + "-sanitySolverTimeout 3600" ], "solc": "solc-0.8.34", "solc_via_ir": true, From d5d4f8ddc07bf812f2bbe791e886807fa53b3220 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Mon, 22 Jun 2026 19:20:02 +0200 Subject: [PATCH 16/18] increase sanity solver timeout --- certora/confs/OnlyExplicitPayerCanLoseTokens.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/certora/confs/OnlyExplicitPayerCanLoseTokens.conf b/certora/confs/OnlyExplicitPayerCanLoseTokens.conf index 8178a56ff..3fa5e11c3 100644 --- a/certora/confs/OnlyExplicitPayerCanLoseTokens.conf +++ b/certora/confs/OnlyExplicitPayerCanLoseTokens.conf @@ -19,6 +19,7 @@ "-depth 5", "-mediumTimeout 30", "-timeout 7200", + "-sanitySolverTimeout 3600", "-destructiveOptimizations twostage", "-havocAllByDefault true" ], From 4ef50f06c76e05c0469fac6f92036ef54f1af832 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Tue, 23 Jun 2026 15:11:28 +0200 Subject: [PATCH 17/18] log taker bytes --- src/Midnight.sol | 3 +- src/libraries/EventsLib.sol | 2 +- test/ContinuousFeeTest.sol | 3 +- test/EventGasCompare.t.sol | 255 ++++++++++++++++++++++++++++++++++++ test/TakeTest.sol | 3 +- 5 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 test/EventGasCompare.t.sol diff --git a/src/Midnight.sol b/src/Midnight.sol index 7be6a7d3f..5752430e3 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -462,7 +462,8 @@ contract Midnight is IMidnight { sellerCreditDecrease, receiver, payer, - takerCallback + takerCallback, + takerCallbackData ); bool wasLocked = UtilsLib.tExchange(LIQUIDATION_LOCK_SLOT, id, seller, true); diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index a2af6889c..05422b819 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -20,7 +20,7 @@ library EventsLib { event SetDefaultContinuousFee(address indexed loanToken, uint256 newContinuousFee); event UpdatePosition(bytes32 indexed id_, address indexed user, uint256 creditDecrease, uint256 pendingFeeDecrease, uint256 accruedFee); event MarketCreated(Market market, bytes32 indexed id_); - event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, Offer offer, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback); + event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, Offer offer, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback, bytes takerCallbackData); event Withdraw(address caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address indexed receiver, uint256 pendingFeeDecrease); event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer); event SupplyCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf); diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 85641738c..609e97c95 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -292,7 +292,8 @@ contract ContinuousFeeTest is BaseTest { exitAmount, lender, otherLender, - address(0) + address(0), + hex"" ); take(exitAmount, lender, _makeBuyOffer(keccak256("lender-exit"))); // lender is taker = seller diff --git a/test/EventGasCompare.t.sol b/test/EventGasCompare.t.sol new file mode 100644 index 000000000..b0da4801c --- /dev/null +++ b/test/EventGasCompare.t.sol @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2025 Morpho Association +pragma solidity ^0.8.0; + +import {Test} from "../lib/forge-std/src/Test.sol"; +import {console2} from "../lib/forge-std/src/console2.sol"; +import {Market, Offer, CollateralParams} from "../src/interfaces/IMidnight.sol"; + +/// @dev Harness that isolates the cost of emitting the Take event in its two variants. +/// Both functions read all inputs from storage (constants would be folded at compile time, +/// storage loads are not), return a value derived from the inputs (defeats DCE), and emit +/// exactly one event. The external-call overhead is identical for both variants and cancels +/// in the delta. +contract EventGasHarness { + // --- full-offer variant (log-more / current branch) --- + event TakeFull( + address caller, + bytes32 indexed id_, + uint256 units, + address indexed taker, + Offer offer, + uint256 buyerAssets, + uint256 sellerAssets, + uint256 consumed, + uint256 buyerPendingFeeIncrease, + uint256 sellerPendingFeeDecrease, + uint256 buyerCreditIncrease, + uint256 sellerCreditDecrease, + address receiver, + address payer, + address takerCallback + ); + + // --- scalar variant (origin/main) --- + event TakeScalar( + address caller, + bytes32 indexed id_, + uint256 units, + address indexed taker, + address indexed maker, + bool offerIsBuy, + bytes32 group, + uint256 buyerAssets, + uint256 sellerAssets, + uint256 consumed, + uint256 buyerPendingFeeIncrease, + uint256 sellerPendingFeeDecrease, + uint256 buyerCreditIncrease, + uint256 sellerCreditDecrease, + address receiver, + address payer + ); + + // storage-backed inputs + Offer internal offer; + address internal caller; + bytes32 internal id_; + uint256 internal units; + address internal taker; + uint256 internal buyerAssets; + uint256 internal sellerAssets; + uint256 internal consumed; + uint256 internal buyerPendingFeeIncrease; + uint256 internal sellerPendingFeeDecrease; + uint256 internal buyerCreditIncrease; + uint256 internal sellerCreditDecrease; + address internal receiver; + address internal payer; + address internal takerCallback; + + function setOffer(Offer memory o) external { + offer = o; + } + + function setScalars( + address _caller, + bytes32 _id, + uint256 _units, + address _taker, + uint256 _buyerAssets, + uint256 _sellerAssets, + uint256 _consumed, + uint256 _buyerPendingFeeIncrease, + uint256 _sellerPendingFeeDecrease, + uint256 _buyerCreditIncrease, + uint256 _sellerCreditDecrease, + address _receiver, + address _payer, + address _takerCallback + ) external { + caller = _caller; + id_ = _id; + units = _units; + taker = _taker; + buyerAssets = _buyerAssets; + sellerAssets = _sellerAssets; + consumed = _consumed; + buyerPendingFeeIncrease = _buyerPendingFeeIncrease; + sellerPendingFeeDecrease = _sellerPendingFeeDecrease; + buyerCreditIncrease = _buyerCreditIncrease; + sellerCreditDecrease = _sellerCreditDecrease; + receiver = _receiver; + payer = _payer; + takerCallback = _takerCallback; + } + + function logFull() external returns (uint256) { + Offer memory o = offer; + emit TakeFull( + caller, + id_, + units, + taker, + o, + buyerAssets, + sellerAssets, + consumed, + buyerPendingFeeIncrease, + sellerPendingFeeDecrease, + buyerCreditIncrease, + sellerCreditDecrease, + receiver, + payer, + takerCallback + ); + // derive a return value from inputs so the call cannot be DCE'd + return units ^ uint256(uint160(caller)) ^ o.market.maturity ^ o.callbackData.length; + } + + function logScalar() external returns (uint256) { + Offer memory o = offer; + emit TakeScalar( + caller, + id_, + units, + taker, + o.maker, + o.buy, + o.group, + buyerAssets, + sellerAssets, + consumed, + buyerPendingFeeIncrease, + sellerPendingFeeDecrease, + buyerCreditIncrease, + sellerCreditDecrease, + receiver, + payer + ); + return units ^ uint256(uint160(caller)) ^ uint256(uint160(o.maker)) ^ uint256(o.group); + } +} + +contract EventGasCompare is Test { + EventGasHarness internal harness; + uint256 internal sink; + + function setUp() public { + harness = new EventGasHarness(); + + harness.setScalars( + address(0xCA11), + bytes32(uint256(0x1D)), + 1e18, + address(0x7A1E), + 123e18, + 456e18, + 789e18, + 111e18, + 222e18, + 333e18, + 444e18, + address(0x4ECE), + address(0xBABE), + address(0xCB) + ); + } + + /// @dev Builds an Offer with `numCollateral` collateral params and `callbackLen` bytes of + /// callbackData. All scalar fields are non-zero. + function _buildOffer(uint256 numCollateral, uint256 callbackLen) internal pure returns (Offer memory o) { + o.market.loanToken = address(0x10A4); + o.market.maturity = 100 days; + o.market.rcfThreshold = 7; + o.market.enterGate = address(0xE47E); + o.market.liquidatorGate = address(0x11D4); + o.market.collateralParams = new CollateralParams[](numCollateral); + for (uint256 i; i < numCollateral; ++i) { + o.market.collateralParams[i] = CollateralParams({ + token: address(uint160(0xC011 + i)), + lltv: 0.77e18 + i, + maxLif: 1.1e18 + i, + oracle: address(uint160(0x04AC + i)) + }); + } + + o.buy = true; + o.maker = address(0x344E); + o.start = 1; + o.expiry = 200 days; + o.tick = 12345; + o.group = bytes32(uint256(0x6409)); + o.callback = address(0xCBAC); + o.callbackData = new bytes(callbackLen); + for (uint256 i; i < callbackLen; ++i) { + o.callbackData[i] = bytes1(uint8(0xAB)); + } + o.receiverIfMakerIsSeller = address(0x4ECE); + o.ratifier = address(0x4A71); + o.reduceOnly = false; + o.maxUnits = type(uint128).max; + o.maxAssets = type(uint128).max; + o.continuousFeeCap = 999; + } + + function _measure(uint256 numCollateral, uint256 callbackLen) + internal + returns (uint256 fullGas, uint256 scalarGas) + { + harness.setOffer(_buildOffer(numCollateral, callbackLen)); + + // warm the storage slots / code with a throwaway call to each, so the measured call + // reflects steady-state cost (cold->warm SLOAD differences would otherwise bias one path). + sink += harness.logFull(); + sink += harness.logScalar(); + + uint256 g = gasleft(); + uint256 r1 = harness.logFull(); + fullGas = g - gasleft(); + + g = gasleft(); + uint256 r2 = harness.logScalar(); + scalarGas = g - gasleft(); + + sink += r1 + r2; + } + + function testEventGasCompare() public { + uint256[3] memory collats = [uint256(0), 1, 3]; + uint256[3] memory cbLens = [uint256(0), 64, 256]; + + console2.log("collats | cbLen | fullGas | scalarGas | delta"); + for (uint256 i; i < collats.length; ++i) { + for (uint256 j; j < cbLens.length; ++j) { + (uint256 fullGas, uint256 scalarGas) = _measure(collats[i], cbLens[j]); + uint256 delta = fullGas - scalarGas; + console2.log(collats[i], cbLens[j], fullGas, scalarGas); + console2.log(" delta:", delta); + } + } + + // keep sink alive + emit log_named_uint("sink", sink); + } +} diff --git a/test/TakeTest.sol b/test/TakeTest.sol index 537968c9f..ccb1031d3 100644 --- a/test/TakeTest.sol +++ b/test/TakeTest.sol @@ -198,7 +198,8 @@ contract TakeTest is BaseTest { existingCredit, receiver, address(payerCallback), - offerIsBuy ? address(0) : address(payerCallback) + offerIsBuy ? address(0) : address(payerCallback), + hex"" ); vm.prank(caller); From 6897e127e078d9ff9f2f6847986187a07ba23797 Mon Sep 17 00:00:00 2001 From: Adrien Husson Date: Tue, 23 Jun 2026 15:23:11 +0200 Subject: [PATCH 18/18] log ratifier data --- src/Midnight.sol | 1 + src/libraries/EventsLib.sol | 2 +- test/ContinuousFeeTest.sol | 1 + test/TakeTest.sol | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Midnight.sol b/src/Midnight.sol index 5752430e3..ecc6137d0 100644 --- a/src/Midnight.sol +++ b/src/Midnight.sol @@ -453,6 +453,7 @@ contract Midnight is IMidnight { units, taker, offer, + ratifierData, buyerAssets, sellerAssets, newConsumed, diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 05422b819..1d22929bd 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -20,7 +20,7 @@ library EventsLib { event SetDefaultContinuousFee(address indexed loanToken, uint256 newContinuousFee); event UpdatePosition(bytes32 indexed id_, address indexed user, uint256 creditDecrease, uint256 pendingFeeDecrease, uint256 accruedFee); event MarketCreated(Market market, bytes32 indexed id_); - event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, Offer offer, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback, bytes takerCallbackData); + event Take(address caller, bytes32 indexed id_, uint256 units, address indexed taker, Offer offer, bytes ratifierData, uint256 buyerAssets, uint256 sellerAssets, uint256 consumed, uint256 buyerPendingFeeIncrease, uint256 sellerPendingFeeDecrease, uint256 buyerCreditIncrease, uint256 sellerCreditDecrease, address receiver, address payer, address takerCallback, bytes takerCallbackData); event Withdraw(address caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address indexed receiver, uint256 pendingFeeDecrease); event Repay(address indexed caller, bytes32 indexed id_, uint256 units, address indexed onBehalf, address payer); event SupplyCollateral(address caller, bytes32 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf); diff --git a/test/ContinuousFeeTest.sol b/test/ContinuousFeeTest.sol index 609e97c95..f77e332a9 100644 --- a/test/ContinuousFeeTest.sol +++ b/test/ContinuousFeeTest.sol @@ -283,6 +283,7 @@ contract ContinuousFeeTest is BaseTest { exitAmount, lender, _makeBuyOffer(keccak256("lender-exit")), + hex"", takeAssets, takeAssets, exitAmount, diff --git a/test/TakeTest.sol b/test/TakeTest.sol index ccb1031d3..276b3c90b 100644 --- a/test/TakeTest.sol +++ b/test/TakeTest.sol @@ -189,6 +189,7 @@ contract TakeTest is BaseTest { units, taker, offer, + hex"", buyerAssets, sellerAssets, existingConsumed + units,