@zoralabs/limit-orders 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/.turbo/turbo-build$colon$js.log +50 -49
  2. package/CHANGELOG.md +73 -0
  3. package/abis/ISetLimitOrderConfig.json +27 -0
  4. package/abis/IWETH.json +118 -0
  5. package/abis/IZoraLimitOrderBook.json +5 -0
  6. package/abis/LimitOrderLiquidity.json +7 -0
  7. package/abis/LimitOrderViews.json +62 -0
  8. package/abis/{SimpleAccessManaged.json → Ownable.json} +29 -10
  9. package/abis/Ownable2Step.json +115 -0
  10. package/abis/PermittedCallers.json +181 -0
  11. package/abis/SwapWithLimitOrders.json +134 -14
  12. package/abis/ZoraLimitOrderBook.json +187 -35
  13. package/cache/solidity-files-cache.json +1 -1
  14. package/dist/index.cjs +219 -34
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +219 -34
  17. package/dist/index.js.map +1 -1
  18. package/dist/wagmiGenerated.d.ts +254 -41
  19. package/dist/wagmiGenerated.d.ts.map +1 -1
  20. package/out/BalanceDelta.sol/BalanceDeltaLibrary.json +1 -1
  21. package/out/BeforeSwapDelta.sol/BeforeSwapDeltaLibrary.json +1 -1
  22. package/out/BitMath.sol/BitMath.json +1 -1
  23. package/out/BytesLib.sol/BytesLib.json +1 -1
  24. package/out/CoinCommon.sol/CoinCommon.json +1 -1
  25. package/out/CoinConfigurationVersions.sol/CoinConfigurationVersions.json +1 -1
  26. package/out/CoinConstants.sol/CoinConstants.json +1 -1
  27. package/out/Context.sol/Context.json +1 -1
  28. package/out/Currency.sol/CurrencyLibrary.json +1 -1
  29. package/out/CurrencyReserves.sol/CurrencyReserves.json +1 -1
  30. package/out/CustomRevert.sol/CustomRevert.json +1 -1
  31. package/out/DopplerMath.sol/DopplerMath.json +1 -1
  32. package/out/FixedPoint128.sol/FixedPoint128.json +1 -1
  33. package/out/FixedPoint96.sol/FixedPoint96.json +1 -1
  34. package/out/FullMath.sol/FullMath.json +1 -1
  35. package/out/IAllowanceTransfer.sol/IAllowanceTransfer.json +1 -1
  36. package/out/ICoin.sol/ICoin.json +1 -1
  37. package/out/ICoin.sol/IHasCoinType.json +1 -1
  38. package/out/ICoin.sol/IHasPoolKey.json +1 -1
  39. package/out/ICoin.sol/IHasSwapPath.json +1 -1
  40. package/out/ICoin.sol/IHasTotalSupplyForPositions.json +1 -1
  41. package/out/IDeployedCoinVersionLookup.sol/IDeployedCoinVersionLookup.json +1 -1
  42. package/out/IDopplerErrors.sol/IDopplerErrors.json +1 -1
  43. package/out/IEIP712.sol/IEIP712.json +1 -1
  44. package/out/IERC1363.sol/IERC1363.json +1 -1
  45. package/out/IERC165.sol/IERC165.json +1 -1
  46. package/out/IERC20.sol/IERC20.json +1 -1
  47. package/out/IERC20Minimal.sol/IERC20Minimal.json +1 -1
  48. package/out/IERC6909Claims.sol/IERC6909Claims.json +1 -1
  49. package/out/IERC7572.sol/IERC7572.json +1 -1
  50. package/out/IExtsload.sol/IExtsload.json +1 -1
  51. package/out/IExttload.sol/IExttload.json +1 -1
  52. package/out/IHasRewardsRecipients.sol/IHasRewardsRecipients.json +1 -1
  53. package/out/IHooks.sol/IHooks.json +1 -1
  54. package/out/IMsgSender.sol/IMsgSender.json +1 -1
  55. package/out/IPoolManager.sol/IPoolManager.json +1 -1
  56. package/out/IProtocolFees.sol/IProtocolFees.json +1 -1
  57. package/out/ISetLimitOrderConfig.sol/ISetLimitOrderConfig.json +1 -0
  58. package/out/ISupportsLimitOrderFill.sol/ISupportsLimitOrderFill.json +1 -1
  59. package/out/ISwapPathRouter.sol/ISwapPathRouter.json +1 -1
  60. package/out/ISwapRouter.sol/ISwapRouter.json +1 -1
  61. package/out/IUniswapV3SwapCallback.sol/IUniswapV3SwapCallback.json +1 -1
  62. package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4Hook.json +1 -1
  63. package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4HookWithUpdateableFee.json +1 -1
  64. package/out/IUpgradeableV4Hook.sol/IUpgradeableV4Hook.json +1 -1
  65. package/out/IWETH.sol/IWETH.json +1 -0
  66. package/out/IZoraHookRegistry.sol/IZoraHookRegistry.json +1 -1
  67. package/out/IZoraLimitOrderBook.sol/IZoraLimitOrderBook.json +1 -1
  68. package/out/IZoraLimitOrderBookCoinsInterface.sol/IZoraLimitOrderBookCoinsInterface.json +1 -1
  69. package/out/IZoraV4CoinHook.sol/IZoraV4CoinHook.json +1 -1
  70. package/out/LimitOrderBitmap.sol/LimitOrderBitmap.json +1 -1
  71. package/out/LimitOrderCommon.sol/LimitOrderCommon.json +1 -1
  72. package/out/LimitOrderCreate.sol/LimitOrderCreate.json +1 -1
  73. package/out/LimitOrderFill.sol/LimitOrderFill.json +1 -1
  74. package/out/LimitOrderLiquidity.sol/LimitOrderLiquidity.json +1 -1
  75. package/out/LimitOrderQueues.sol/LimitOrderQueues.json +1 -1
  76. package/out/LimitOrderStorage.sol/LimitOrderStorage.json +1 -1
  77. package/out/LimitOrderTypes.sol/LimitOrderTypes.json +1 -1
  78. package/out/LimitOrderViews.sol/LimitOrderViews.json +1 -0
  79. package/out/LimitOrderWithdraw.sol/LimitOrderWithdraw.json +1 -1
  80. package/out/LiquidityAmounts.sol/LiquidityAmounts.json +1 -1
  81. package/out/LiquidityMath.sol/LiquidityMath.json +1 -1
  82. package/out/Lock.sol/Lock.json +1 -1
  83. package/out/NonzeroDeltaCount.sol/NonzeroDeltaCount.json +1 -1
  84. package/out/Ownable.sol/Ownable.json +1 -0
  85. package/out/Ownable2Step.sol/Ownable2Step.json +1 -0
  86. package/out/Path.sol/Path.json +1 -1
  87. package/out/PathKey.sol/PathKeyLibrary.json +1 -1
  88. package/out/Permit2Payments.sol/Permit2Payments.json +1 -1
  89. package/out/PermittedCallers.sol/PermittedCallers.json +1 -0
  90. package/out/PoolId.sol/PoolIdLibrary.json +1 -1
  91. package/out/Position.sol/Position.json +1 -1
  92. package/out/SafeCast.sol/SafeCast.json +1 -1
  93. package/out/SafeCast160.sol/SafeCast160.json +1 -1
  94. package/out/SafeERC20.sol/SafeERC20.json +1 -1
  95. package/out/SqrtPriceMath.sol/SqrtPriceMath.json +1 -1
  96. package/out/StateLibrary.sol/StateLibrary.json +1 -1
  97. package/out/SwapLimitOrders.sol/SwapLimitOrders.json +1 -1
  98. package/out/SwapWithLimitOrders.sol/SwapWithLimitOrders.json +1 -1
  99. package/out/TickBitmap.sol/TickBitmap.json +1 -1
  100. package/out/TickMath.sol/TickMath.json +1 -1
  101. package/out/TransientSlot.sol/TransientSlot.json +1 -1
  102. package/out/TransientStateLibrary.sol/TransientStateLibrary.json +1 -1
  103. package/out/UniV4SwapToCurrency.sol/UniV4SwapToCurrency.json +1 -1
  104. package/out/UnsafeMath.sol/UnsafeMath.json +1 -1
  105. package/out/V3ToV4SwapLib.sol/V3ToV4SwapLib.json +1 -1
  106. package/out/ZoraLimitOrderBook.sol/ZoraLimitOrderBook.json +1 -1
  107. package/out/build-info/37e0124d88d60569.json +1 -0
  108. package/out/uniswap/BitMath.sol/BitMath.json +1 -1
  109. package/out/uniswap/CustomRevert.sol/CustomRevert.json +1 -1
  110. package/out/uniswap/FullMath.sol/FullMath.json +1 -1
  111. package/out/uniswap/SafeCast.sol/SafeCast.json +1 -1
  112. package/out/uniswap/TickMath.sol/TickMath.json +1 -1
  113. package/package/wagmiGenerated.ts +218 -33
  114. package/package.json +1 -1
  115. package/src/IZoraLimitOrderBook.sol +5 -5
  116. package/src/ZoraLimitOrderBook.sol +24 -41
  117. package/src/access/PermittedCallers.sol +41 -0
  118. package/src/libs/LimitOrderBitmap.sol +0 -51
  119. package/src/libs/LimitOrderCommon.sol +48 -30
  120. package/src/libs/LimitOrderCreate.sol +5 -18
  121. package/src/libs/LimitOrderFill.sol +32 -161
  122. package/src/libs/LimitOrderLiquidity.sol +92 -71
  123. package/src/libs/LimitOrderViews.sol +168 -0
  124. package/src/libs/LimitOrderWithdraw.sol +13 -4
  125. package/src/libs/SwapLimitOrders.sol +14 -7
  126. package/src/router/ISetLimitOrderConfig.sol +12 -0
  127. package/src/router/SwapWithLimitOrders.sol +46 -33
  128. package/test/LimitOrderAccessControl.t.sol +173 -156
  129. package/test/LimitOrderBitmap.t.sol +13 -7
  130. package/test/LimitOrderFill.t.sol +42 -4
  131. package/test/LimitOrderLibraries.t.sol +18 -10
  132. package/test/LimitOrderLiquidityPayouts.t.sol +280 -3
  133. package/test/LimitOrderWithdraw.t.sol +28 -1
  134. package/test/SwapWithLimitOrders.t.sol +3 -5
  135. package/test/SwapWithLimitOrdersRouter.t.sol +108 -13
  136. package/test/gas/LimitOrderFillGas.t.sol +0 -7
  137. package/test/gas/LimitOrderSwapGas.t.sol +0 -6
  138. package/test/unit/LimitOrderBitmapUnit.t.sol +0 -134
  139. package/test/unit/LimitOrderCreateUnit.t.sol +32 -0
  140. package/test/unit/SwapLimitOrdersUnit.t.sol +231 -33
  141. package/test/unit/SwapLimitOrdersValidation.t.sol +28 -42
  142. package/test/unit/SwapWithLimitOrdersUnit.t.sol +21 -88
  143. package/test/utils/BaseTest.sol +34 -22
  144. package/test/utils/MockWETH.sol +39 -0
  145. package/test/utils/TestableZoraLimitOrderBook.sol +5 -7
  146. package/abis/IAuthority.json +0 -31
  147. package/abis/SimpleAccessManager.json +0 -351
  148. package/out/IAuthority.sol/IAuthority.json +0 -1
  149. package/out/SimpleAccessManaged.sol/SimpleAccessManaged.json +0 -1
  150. package/out/SimpleAccessManager.sol/SimpleAccessManager.json +0 -1
  151. package/out/build-info/69718f10d1dc37f0.json +0 -1
  152. package/src/access/SimpleAccessManaged.sol +0 -76
  153. package/src/access/SimpleAccessManager.sol +0 -268
  154. package/test/SimpleAccessManager.t.sol +0 -420
@@ -2,25 +2,28 @@
2
2
  pragma solidity ^0.8.28;
3
3
 
4
4
  import {BaseTest} from "./utils/BaseTest.sol";
5
- import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol";
6
- import {SimpleAccessManaged} from "../src/access/SimpleAccessManaged.sol";
5
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
7
6
  import {IZoraLimitOrderBook} from "../src/IZoraLimitOrderBook.sol";
7
+ import {PermittedCallers} from "../src/access/PermittedCallers.sol";
8
8
  import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
9
9
  import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
10
10
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
11
11
  import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
12
+ import {SwapWithLimitOrders} from "../src/router/SwapWithLimitOrders.sol";
13
+ import {LimitOrderConfig} from "../src/libs/SwapLimitOrders.sol";
12
14
 
13
15
  contract LimitOrderAccessControlTest is BaseTest {
14
- uint64 public constant CREATOR_ROLE = 1;
15
16
  address public unauthorizedUser;
16
- address public authorizedRouter;
17
+ address public authorizedCaller;
18
+ address public newOwner;
17
19
 
18
20
  function setUp() public override {
19
21
  super.setUpNonForked();
20
22
 
21
23
  // Set up test users
22
24
  unauthorizedUser = makeAddr("unauthorizedUser");
23
- authorizedRouter = makeAddr("authorizedRouter");
25
+ authorizedCaller = makeAddr("authorizedCaller");
26
+ newOwner = makeAddr("newOwner");
24
27
  }
25
28
 
26
29
  function _prepareOrder(
@@ -69,120 +72,146 @@ contract LimitOrderAccessControlTest is BaseTest {
69
72
  zoraHookRegistry.registerHooks(hooks, tags);
70
73
  }
71
74
 
72
- function test_create_worksWithPublicRole() public {
75
+ function _setPublicAccess(bool isPublic) internal {
76
+ address[] memory callers = new address[](1);
77
+ callers[0] = address(0); // PUBLIC_ACCESS sentinel
78
+ bool[] memory permitted = new bool[](1);
79
+ permitted[0] = isPublic;
80
+ limitOrderBook.setPermittedCallers(callers, permitted);
81
+ }
82
+
83
+ // Test: create() works in public mode (default)
84
+ function test_create_publicMode() public {
73
85
  PoolKey memory key = creatorCoin.getPoolKey();
74
86
 
87
+ // Verify any address is permitted by default (public mode)
88
+ assertTrue(limitOrderBook.isPermittedCaller(unauthorizedUser), "any address should be permitted in public mode");
89
+
90
+ // Anyone can create orders in public mode
75
91
  (bool isCurrency0, address orderCoin, uint256[] memory orderSizes, int24[] memory orderTicks) = _prepareOrder(unauthorizedUser, key);
76
92
  _createOrder(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser, orderCoin);
77
93
  }
78
94
 
79
- function test_transitionToPermissioned() public {
95
+ // Test: setPermittedCallers can toggle public access control via address(0)
96
+ function test_setPermittedCallers_togglesPublicAccessControl() public {
80
97
  PoolKey memory key = creatorCoin.getPoolKey();
81
98
 
82
- // Initially anyone can create orders (PUBLIC_ROLE is set in BaseTest)
99
+ // Initially public - anyone can create
100
+ assertTrue(limitOrderBook.isPermittedCaller(unauthorizedUser));
83
101
  (bool isCurrency0, address orderCoin, uint256[] memory orderSizes, int24[] memory orderTicks) = _prepareOrder(unauthorizedUser, key);
84
102
  _createOrder(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser, orderCoin);
85
103
 
86
- // Create a specific role for authorized creators
87
- accessManager.labelRole(CREATOR_ROLE, "CREATOR");
88
-
89
- // Grant role to authorized router
90
- accessManager.grantRole(CREATOR_ROLE, authorizedRouter, 0);
91
-
92
- // Switch function to require CREATOR_ROLE
93
- bytes4[] memory selectors = new bytes4[](1);
94
- selectors[0] = IZoraLimitOrderBook.create.selector;
95
- accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, CREATOR_ROLE);
104
+ // Owner sets to permissioned mode by disabling address(0)
105
+ _setPublicAccess(false);
106
+ assertFalse(limitOrderBook.isPermittedCaller(unauthorizedUser));
96
107
 
97
108
  // Now unauthorized user should fail
98
109
  (isCurrency0, orderCoin, orderSizes, orderTicks) = _prepareOrder(unauthorizedUser, key);
99
- vm.expectRevert(SimpleAccessManaged.AccessManagedUnauthorized.selector);
110
+ vm.expectRevert(PermittedCallers.CallerNotPermitted.selector);
100
111
  limitOrderBook.create{value: orderCoin == address(0) ? 1 ether : 0}(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser);
101
112
  vm.stopPrank();
102
113
 
103
- // But authorized router should succeed
104
- (isCurrency0, orderCoin, orderSizes, orderTicks) = _prepareOrder(authorizedRouter, key);
105
- _createOrder(key, isCurrency0, orderSizes, orderTicks, authorizedRouter, orderCoin);
114
+ // Owner sets back to public mode
115
+ _setPublicAccess(true);
116
+ assertTrue(limitOrderBook.isPermittedCaller(unauthorizedUser));
106
117
 
107
- // If we got here without reverting, the test passed
118
+ // Now anyone can create again
119
+ (isCurrency0, orderCoin, orderSizes, orderTicks) = _prepareOrder(unauthorizedUser, key);
120
+ _createOrder(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser, orderCoin);
108
121
  }
109
122
 
110
- function test_unauthorizedUserCannotCreate() public {
123
+ // Test: unauthorized user cannot create in permissioned mode
124
+ function test_create_permissionedMode_unauthorizedFails() public {
111
125
  PoolKey memory key = creatorCoin.getPoolKey();
112
126
 
113
- // Set up permissioned mode
114
- accessManager.labelRole(CREATOR_ROLE, "CREATOR");
115
- accessManager.grantRole(CREATOR_ROLE, authorizedRouter, 0);
116
-
117
- bytes4[] memory selectors = new bytes4[](1);
118
- selectors[0] = IZoraLimitOrderBook.create.selector;
119
- accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, CREATOR_ROLE);
127
+ // Set to permissioned mode
128
+ _setPublicAccess(false);
120
129
 
121
130
  // Unauthorized user tries to create
122
131
  (bool isCurrency0, address orderCoin, uint256[] memory orderSizes, int24[] memory orderTicks) = _prepareOrder(unauthorizedUser, key);
123
- vm.expectRevert(SimpleAccessManaged.AccessManagedUnauthorized.selector);
132
+ vm.expectRevert(PermittedCallers.CallerNotPermitted.selector);
124
133
  limitOrderBook.create{value: orderCoin == address(0) ? 1 ether : 0}(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser);
125
134
  vm.stopPrank();
126
135
  }
127
136
 
128
- function test_grantAndRevokeRole() public {
137
+ // Test: setPermittedCallers grants and revokes access
138
+ function test_setPermittedCallers_grantsAndRevokesAccess() public {
129
139
  PoolKey memory key = creatorCoin.getPoolKey();
130
140
 
131
- // Set up permissioned mode
132
- accessManager.labelRole(CREATOR_ROLE, "CREATOR");
133
-
134
- bytes4[] memory selectors = new bytes4[](1);
135
- selectors[0] = IZoraLimitOrderBook.create.selector;
136
- accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, CREATOR_ROLE);
141
+ // Set to permissioned mode
142
+ _setPublicAccess(false);
137
143
 
138
144
  // Initially unauthorized user cannot create
139
145
  (bool isCurrency0, address orderCoin, uint256[] memory orderSizes, int24[] memory orderTicks) = _prepareOrder(unauthorizedUser, key);
140
- vm.expectRevert(SimpleAccessManaged.AccessManagedUnauthorized.selector);
146
+ vm.expectRevert(PermittedCallers.CallerNotPermitted.selector);
141
147
  limitOrderBook.create{value: orderCoin == address(0) ? 1 ether : 0}(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser);
142
148
  vm.stopPrank();
143
149
 
144
- // Grant role to user
145
- accessManager.grantRole(CREATOR_ROLE, unauthorizedUser, 0);
150
+ // Grant access to user
151
+ address[] memory callers = new address[](1);
152
+ callers[0] = unauthorizedUser;
153
+ bool[] memory permitted = new bool[](1);
154
+ permitted[0] = true;
155
+ limitOrderBook.setPermittedCallers(callers, permitted);
156
+
157
+ assertTrue(limitOrderBook.isPermittedCaller(unauthorizedUser), "user should be permitted");
146
158
 
147
159
  // Now user can create
148
160
  (isCurrency0, orderCoin, orderSizes, orderTicks) = _prepareOrder(unauthorizedUser, key);
149
161
  _createOrder(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser, orderCoin);
150
162
 
151
- // Revoke role
152
- accessManager.revokeRole(CREATOR_ROLE, unauthorizedUser);
163
+ // Revoke access
164
+ permitted[0] = false;
165
+ limitOrderBook.setPermittedCallers(callers, permitted);
166
+
167
+ assertFalse(limitOrderBook.isPermittedCaller(unauthorizedUser), "user should not be permitted");
153
168
 
154
169
  // Now user cannot create again
155
170
  (isCurrency0, orderCoin, orderSizes, orderTicks) = _prepareOrder(unauthorizedUser, key);
156
- vm.expectRevert(SimpleAccessManaged.AccessManagedUnauthorized.selector);
171
+ vm.expectRevert(PermittedCallers.CallerNotPermitted.selector);
157
172
  limitOrderBook.create{value: orderCoin == address(0) ? 1 ether : 0}(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser);
158
173
  vm.stopPrank();
159
174
  }
160
175
 
161
- function test_adminCanReconfigure() public {
162
- PoolKey memory key = creatorCoin.getPoolKey();
163
-
164
- // Admin sets up initial permissioned mode
165
- accessManager.labelRole(CREATOR_ROLE, "CREATOR");
166
- accessManager.grantRole(CREATOR_ROLE, authorizedRouter, 0);
167
-
168
- bytes4[] memory selectors = new bytes4[](1);
169
- selectors[0] = IZoraLimitOrderBook.create.selector;
170
- accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, CREATOR_ROLE);
171
-
172
- // Unauthorized user cannot create
173
- (bool isCurrency0, address orderCoin, uint256[] memory orderSizes, int24[] memory orderTicks) = _prepareOrder(unauthorizedUser, key);
174
- vm.expectRevert(SimpleAccessManaged.AccessManagedUnauthorized.selector);
175
- limitOrderBook.create{value: orderCoin == address(0) ? 1 ether : 0}(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser);
176
- vm.stopPrank();
177
-
178
- // Admin decides to open it back up to public
179
- accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, accessManager.PUBLIC_ROLE());
180
-
181
- // Now anyone can create again
182
- (isCurrency0, orderCoin, orderSizes, orderTicks) = _prepareOrder(unauthorizedUser, key);
183
- _createOrder(key, isCurrency0, orderSizes, orderTicks, unauthorizedUser, orderCoin);
176
+ // Test: setPermittedCallers works with multiple addresses
177
+ function test_setPermittedCallers_batchUpdate() public {
178
+ address caller1 = makeAddr("caller1");
179
+ address caller2 = makeAddr("caller2");
180
+ address caller3 = makeAddr("caller3");
181
+
182
+ _setPublicAccess(false);
183
+
184
+ // Grant access to multiple callers
185
+ address[] memory callers = new address[](3);
186
+ callers[0] = caller1;
187
+ callers[1] = caller2;
188
+ callers[2] = caller3;
189
+ // set public to false
190
+ bool[] memory permitted = new bool[](3);
191
+ permitted[0] = true;
192
+ permitted[1] = true;
193
+ permitted[2] = true;
194
+
195
+ limitOrderBook.setPermittedCallers(callers, permitted);
196
+
197
+ assertTrue(limitOrderBook.isPermittedCaller(caller1));
198
+ assertTrue(limitOrderBook.isPermittedCaller(caller2));
199
+ assertTrue(limitOrderBook.isPermittedCaller(caller3));
200
+
201
+ // Revoke access from caller2
202
+ address[] memory revokeList = new address[](1);
203
+ revokeList[0] = caller2;
204
+ bool[] memory revokePermitted = new bool[](1);
205
+ revokePermitted[0] = false;
206
+
207
+ limitOrderBook.setPermittedCallers(revokeList, revokePermitted);
208
+
209
+ assertTrue(limitOrderBook.isPermittedCaller(caller1));
210
+ assertFalse(limitOrderBook.isPermittedCaller(caller2));
211
+ assertTrue(limitOrderBook.isPermittedCaller(caller3));
184
212
  }
185
213
 
214
+ // Test: non-hook cannot fill while unlocked
186
215
  function test_nonHookCannotFillWhileUnlocked() public {
187
216
  PoolKey memory key = creatorCoin.getPoolKey();
188
217
 
@@ -194,6 +223,7 @@ contract LimitOrderAccessControlTest is BaseTest {
194
223
  caller.attemptUnlockedFill(key, false, -type(int24).max, type(int24).max, 1, address(0));
195
224
  }
196
225
 
226
+ // Test: registered hook can fill while unlocked
197
227
  function test_fillRegisteredHookCanFillWhileUnlocked() public {
198
228
  PoolKey memory key = creatorCoin.getPoolKey();
199
229
 
@@ -217,6 +247,7 @@ contract LimitOrderAccessControlTest is BaseTest {
217
247
  assertEq(_makerBalance(users.seller, created[0].coin), 0, "maker balance should be zero");
218
248
  }
219
249
 
250
+ // Test: unregistered hook cannot fill while unlocked
220
251
  function test_fillUnregisteredHookCannotFillWhileUnlocked() public {
221
252
  PoolKey memory key = creatorCoin.getPoolKey();
222
253
  UnlockedFillCaller caller = new UnlockedFillCaller(address(limitOrderBook), address(poolManager));
@@ -225,6 +256,7 @@ contract LimitOrderAccessControlTest is BaseTest {
225
256
  caller.attemptUnlockedFill(key, true, -type(int24).max, type(int24).max, 5, address(0));
226
257
  }
227
258
 
259
+ // Test: fill maxFillCount defaults to storage
228
260
  function test_fill_MaxFillCountDefaultsToStorage() public {
229
261
  PoolKey memory key = creatorCoin.getPoolKey();
230
262
 
@@ -237,7 +269,6 @@ contract LimitOrderAccessControlTest is BaseTest {
237
269
  _movePriceBeyondTicksWithAutoFillDisabled(created);
238
270
 
239
271
  uint256 previousMax = limitOrderBook.getMaxFillCount();
240
- vm.prank(users.factoryOwner);
241
272
  limitOrderBook.setMaxFillCount(2);
242
273
  (int24 startTick, int24 endTick) = _tickWindow(created, key);
243
274
 
@@ -246,10 +277,10 @@ contract LimitOrderAccessControlTest is BaseTest {
246
277
  FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
247
278
  assertEq(fills.length, 2, "should use stored maxFillCount when input is zero");
248
279
 
249
- vm.prank(users.factoryOwner);
250
280
  limitOrderBook.setMaxFillCount(previousMax);
251
281
  }
252
282
 
283
+ // Test: fillBatch ignores empty order arrays
253
284
  function test_fillBatchIgnoresEmptyOrderArrays() public {
254
285
  PoolKey memory key = creatorCoin.getPoolKey();
255
286
 
@@ -288,138 +319,128 @@ contract LimitOrderAccessControlTest is BaseTest {
288
319
  assertApproxEqAbs(makerBalanceBefore - makerBalanceAfter, expectedDelta, 3, "unexpected maker balance delta");
289
320
  }
290
321
 
322
+ // Test: unlockCallback reverts for non-poolManager
291
323
  function test_unlockCallbackRevertsForNonPoolManager() public {
292
324
  vm.expectRevert(IZoraLimitOrderBook.NotPoolManager.selector);
293
325
  limitOrderBook.unlockCallback(bytes(""));
294
326
  }
295
327
 
328
+ // Test: receive reverts for non-poolManager
296
329
  function test_receiveRevertsForNonPoolManager() public {
297
330
  vm.deal(address(this), 1 ether);
298
331
  vm.expectRevert(IZoraLimitOrderBook.NotPoolManager.selector);
299
332
  payable(address(limitOrderBook)).transfer(1 wei);
300
333
  }
301
334
 
302
- function test_setMaxFillCount_worksWithPublicRole() public {
335
+ // Test: setMaxFillCount - owner can set
336
+ function test_setMaxFillCount_ownerCanSet() public {
303
337
  // Initially max fill count should be 50 (set in BaseTest)
304
338
  assertEq(limitOrderBook.getMaxFillCount(), 50);
305
339
 
306
- // setMaxFillCount is already configured with PUBLIC_ROLE in BaseTest
307
- // Any user should be able to set it
308
- vm.prank(unauthorizedUser);
340
+ // Owner (this contract) should be able to set it
309
341
  limitOrderBook.setMaxFillCount(20);
310
342
 
311
343
  assertEq(limitOrderBook.getMaxFillCount(), 20);
312
344
  }
313
345
 
346
+ // Test: setMaxFillCount - unauthorized user cannot set
314
347
  function test_setMaxFillCount_unauthorizedUserCannotSet() public {
315
- uint64 MAX_FILL_COUNT_ROLE = 2;
316
-
317
- // Set up permissioned mode for setMaxFillCount
318
- accessManager.labelRole(MAX_FILL_COUNT_ROLE, "MAX_FILL_COUNT_SETTER");
319
- accessManager.grantRole(MAX_FILL_COUNT_ROLE, authorizedRouter, 0);
320
-
321
- bytes4[] memory selectors = new bytes4[](1);
322
- selectors[0] = IZoraLimitOrderBook.setMaxFillCount.selector;
323
- accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, MAX_FILL_COUNT_ROLE);
324
-
325
348
  // Unauthorized user tries to set max fill count
326
349
  vm.prank(unauthorizedUser);
327
- vm.expectRevert(SimpleAccessManaged.AccessManagedUnauthorized.selector);
350
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, unauthorizedUser));
328
351
  limitOrderBook.setMaxFillCount(20);
329
352
 
330
353
  // Verify value hasn't changed (still 50 from BaseTest)
331
354
  assertEq(limitOrderBook.getMaxFillCount(), 50);
332
355
  }
333
356
 
334
- function test_setMaxFillCount_authorizedUserCanSet() public {
335
- uint64 MAX_FILL_COUNT_ROLE = 2;
336
-
337
- // Set up permissioned mode
338
- accessManager.labelRole(MAX_FILL_COUNT_ROLE, "MAX_FILL_COUNT_SETTER");
339
- accessManager.grantRole(MAX_FILL_COUNT_ROLE, authorizedRouter, 0);
340
-
341
- bytes4[] memory selectors = new bytes4[](1);
342
- selectors[0] = IZoraLimitOrderBook.setMaxFillCount.selector;
343
- accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, MAX_FILL_COUNT_ROLE);
357
+ // Test: setPermittedCallers - owner can set
358
+ function test_setPermittedCallers_ownerCanSet() public {
359
+ address[] memory callers = new address[](1);
360
+ callers[0] = authorizedCaller;
361
+ bool[] memory permitted = new bool[](1);
362
+ permitted[0] = true;
344
363
 
345
- // Authorized user sets max fill count
346
- vm.prank(authorizedRouter);
347
- limitOrderBook.setMaxFillCount(25);
348
-
349
- assertEq(limitOrderBook.getMaxFillCount(), 25);
364
+ limitOrderBook.setPermittedCallers(callers, permitted);
365
+ assertTrue(limitOrderBook.isPermittedCaller(authorizedCaller));
350
366
  }
351
367
 
352
- function test_setMaxFillCount_adminCanSet() public {
353
- uint64 MAX_FILL_COUNT_ROLE = 2;
368
+ // Test: setPermittedCallers - unauthorized user cannot set
369
+ function test_setPermittedCallers_unauthorizedUserCannotSet() public {
370
+ address[] memory callers = new address[](0);
371
+ bool[] memory permitted = new bool[](0);
354
372
 
355
- // Set up permissioned mode
356
- accessManager.labelRole(MAX_FILL_COUNT_ROLE, "MAX_FILL_COUNT_SETTER");
373
+ vm.prank(unauthorizedUser);
374
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, unauthorizedUser));
375
+ limitOrderBook.setPermittedCallers(callers, permitted);
376
+ }
357
377
 
358
- bytes4[] memory selectors = new bytes4[](1);
359
- selectors[0] = IZoraLimitOrderBook.setMaxFillCount.selector;
360
- accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, MAX_FILL_COUNT_ROLE);
378
+ // Test: setLimitOrderConfig - owner can set
379
+ function test_setLimitOrderConfig_ownerCanSet() public {
380
+ // Owner (this contract) should be able to set limit order config
381
+ uint256[] memory multiples = new uint256[](2);
382
+ multiples[0] = 2e18; // 2x
383
+ multiples[1] = 3e18; // 3x
361
384
 
362
- // Grant the role to admin explicitly
363
- accessManager.grantRole(MAX_FILL_COUNT_ROLE, address(this), 0);
385
+ uint256[] memory percentages = new uint256[](2);
386
+ percentages[0] = 5000; // 50%
387
+ percentages[1] = 5000; // 50%
364
388
 
365
- // Admin (this contract) should be able to set it with the granted role
366
- limitOrderBook.setMaxFillCount(30);
389
+ LimitOrderConfig memory config = LimitOrderConfig({multiples: multiples, percentages: percentages});
367
390
 
368
- assertEq(limitOrderBook.getMaxFillCount(), 30);
391
+ swapWithLimitOrders.setLimitOrderConfig(config);
369
392
  }
370
393
 
371
- function test_setMaxFillCount_grantAndRevokeRole() public {
372
- uint64 MAX_FILL_COUNT_ROLE = 2;
394
+ // Test: setLimitOrderConfig - unauthorized user cannot set
395
+ function test_setLimitOrderConfig_unauthorizedUserCannotSet() public {
396
+ uint256[] memory multiples = new uint256[](2);
397
+ multiples[0] = 2e18;
398
+ multiples[1] = 3e18;
373
399
 
374
- // Set up permissioned mode
375
- accessManager.labelRole(MAX_FILL_COUNT_ROLE, "MAX_FILL_COUNT_SETTER");
400
+ uint256[] memory percentages = new uint256[](2);
401
+ percentages[0] = 5000;
402
+ percentages[1] = 5000;
376
403
 
377
- bytes4[] memory selectors = new bytes4[](1);
378
- selectors[0] = IZoraLimitOrderBook.setMaxFillCount.selector;
379
- accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, MAX_FILL_COUNT_ROLE);
404
+ LimitOrderConfig memory config = LimitOrderConfig({multiples: multiples, percentages: percentages});
380
405
 
381
- // Initially unauthorized user cannot set
406
+ // Unauthorized user tries to set config
382
407
  vm.prank(unauthorizedUser);
383
- vm.expectRevert(SimpleAccessManaged.AccessManagedUnauthorized.selector);
384
- limitOrderBook.setMaxFillCount(15);
408
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, unauthorizedUser));
409
+ swapWithLimitOrders.setLimitOrderConfig(config);
410
+ }
385
411
 
386
- // Grant role to user
387
- accessManager.grantRole(MAX_FILL_COUNT_ROLE, unauthorizedUser, 0);
412
+ // Test: ownership transfer (two-step process)
413
+ function test_ownershipTransfer() public {
414
+ // Initial owner is this contract
415
+ assertEq(limitOrderBook.owner(), address(this));
388
416
 
389
- // Now user can set
390
- vm.prank(unauthorizedUser);
391
- limitOrderBook.setMaxFillCount(15);
392
- assertEq(limitOrderBook.getMaxFillCount(), 15);
417
+ // Step 1: Current owner proposes transfer
418
+ limitOrderBook.transferOwnership(newOwner);
393
419
 
394
- // Revoke role
395
- accessManager.revokeRole(MAX_FILL_COUNT_ROLE, unauthorizedUser);
420
+ // Ownership hasn't changed yet
421
+ assertEq(limitOrderBook.owner(), address(this));
422
+ assertEq(limitOrderBook.pendingOwner(), newOwner);
396
423
 
397
- // Now user cannot set again
398
- vm.prank(unauthorizedUser);
399
- vm.expectRevert(SimpleAccessManaged.AccessManagedUnauthorized.selector);
400
- limitOrderBook.setMaxFillCount(20);
424
+ // New owner cannot perform owner actions yet
425
+ vm.prank(newOwner);
426
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, newOwner));
427
+ limitOrderBook.setMaxFillCount(100);
401
428
 
402
- // Verify value hasn't changed from last successful set
403
- assertEq(limitOrderBook.getMaxFillCount(), 15);
404
- }
429
+ // Step 2: New owner accepts transfer
430
+ vm.prank(newOwner);
431
+ limitOrderBook.acceptOwnership();
405
432
 
406
- function test_setAuthority_revertsForUnauthorizedCaller() public {
407
- address newAuthority = address(new AccessManager(address(this)));
433
+ // Now ownership has transferred
434
+ assertEq(limitOrderBook.owner(), newOwner);
408
435
 
409
- vm.prank(unauthorizedUser);
410
- vm.expectRevert(SimpleAccessManaged.AccessManagedUnauthorized.selector);
411
- limitOrderBook.setAuthority(newAuthority);
412
- }
413
-
414
- function test_setAuthority_revertsForNonContractAddress() public {
415
- // Deploy a simple test contract with test contract as authority
416
- AuthorityTester tester = new AuthorityTester(address(this));
417
-
418
- address eoaAddress = makeAddr("eoa");
436
+ // New owner can perform owner actions
437
+ vm.prank(newOwner);
438
+ limitOrderBook.setMaxFillCount(100);
439
+ assertEq(limitOrderBook.getMaxFillCount(), 100);
419
440
 
420
- // Try to set EOA as authority - should revert with AccessManagedInvalidAuthority
421
- vm.expectRevert(abi.encodeWithSelector(SimpleAccessManaged.AccessManagedInvalidAuthority.selector, eoaAddress));
422
- tester.setAuthority(eoaAddress);
441
+ // Old owner cannot perform owner actions
442
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(this)));
443
+ limitOrderBook.setMaxFillCount(50);
423
444
  }
424
445
  }
425
446
 
@@ -455,7 +476,3 @@ contract UnlockedFillCaller {
455
476
  return bytes("");
456
477
  }
457
478
  }
458
-
459
- contract AuthorityTester is SimpleAccessManaged {
460
- constructor(address initialAuthority) SimpleAccessManaged(initialAuthority) {}
461
- }
@@ -30,7 +30,8 @@ contract LimitOrderBitmapTest is BaseTest {
30
30
 
31
31
  // Verify all ticks are initialized in bitmap
32
32
  for (uint256 i; i < orderTicks.length; ++i) {
33
- assertTrue(_isTickInitialized(poolKeyHash, orderCoin, orderTicks[i], key.tickSpacing), "tick should be initialized");
33
+ int24 fillableTick = _fillableTick(isCurrency0, orderTicks[i], key.tickSpacing);
34
+ assertTrue(_isTickInitialized(poolKeyHash, orderCoin, fillableTick, key.tickSpacing), "tick should be initialized");
34
35
  }
35
36
  }
36
37
 
@@ -52,7 +53,8 @@ contract LimitOrderBitmapTest is BaseTest {
52
53
  bytes32 poolKeyHash = created[0].poolKeyHash;
53
54
 
54
55
  // Verify tick is initialized
55
- assertTrue(_isTickInitialized(poolKeyHash, orderCoin, orderTicks[0], key.tickSpacing), "tick should be initialized after create");
56
+ int24 fillableTick = _fillableTick(isCurrency0, orderTicks[0], key.tickSpacing);
57
+ assertTrue(_isTickInitialized(poolKeyHash, orderCoin, fillableTick, key.tickSpacing), "tick should be initialized after create");
56
58
 
57
59
  // Withdraw order
58
60
  bytes32[] memory orderIds = new bytes32[](1);
@@ -61,7 +63,7 @@ contract LimitOrderBitmapTest is BaseTest {
61
63
  limitOrderBook.withdraw(orderIds, orderCoin, 0, users.seller);
62
64
 
63
65
  // Verify tick is cleared in bitmap
64
- assertFalse(_isTickInitialized(poolKeyHash, orderCoin, orderTicks[0], key.tickSpacing), "tick should be cleared after withdraw");
66
+ assertFalse(_isTickInitialized(poolKeyHash, orderCoin, fillableTick, key.tickSpacing), "tick should be cleared after withdraw");
65
67
  }
66
68
 
67
69
  function test_bitmap_RemainsSetWithPartialOrders() public {
@@ -95,7 +97,8 @@ contract LimitOrderBitmapTest is BaseTest {
95
97
  limitOrderBook.withdraw(orderIds, orderCoin, 0, users.seller);
96
98
 
97
99
  // Bitmap should still be set because second order remains
98
- assertTrue(_isTickInitialized(poolKeyHash, orderCoin, tick, key.tickSpacing), "tick should remain initialized with remaining order");
100
+ int24 fillableTick = _fillableTick(isCurrency0, tick, key.tickSpacing);
101
+ assertTrue(_isTickInitialized(poolKeyHash, orderCoin, fillableTick, key.tickSpacing), "tick should remain initialized with remaining order");
99
102
  }
100
103
 
101
104
  function test_bitmap_wordBoundaries() public {
@@ -129,7 +132,8 @@ contract LimitOrderBitmapTest is BaseTest {
129
132
 
130
133
  // Verify all boundary ticks are initialized
131
134
  for (uint256 i = 0; i < boundaryTicks.length; i++) {
132
- assertTrue(_isTickInitialized(poolKeyHash, orderCoin, boundaryTicks[i], spacing), "boundary tick should be initialized");
135
+ int24 fillableTick = _fillableTick(isCurrency0, boundaryTicks[i], spacing);
136
+ assertTrue(_isTickInitialized(poolKeyHash, orderCoin, fillableTick, spacing), "boundary tick should be initialized");
133
137
  }
134
138
  }
135
139
 
@@ -171,8 +175,10 @@ contract LimitOrderBitmapTest is BaseTest {
171
175
  bytes32 poolKeyHash = created[0].poolKeyHash;
172
176
 
173
177
  // Verify extreme ticks are initialized
174
- assertTrue(_isTickInitialized(poolKeyHash, orderCoin, extremeTicks[0], spacing), "far tick 1 should be initialized");
175
- assertTrue(_isTickInitialized(poolKeyHash, orderCoin, extremeTicks[1], spacing), "far tick 2 should be initialized");
178
+ int24 fillableTick0 = _fillableTick(isCurrency0, extremeTicks[0], spacing);
179
+ int24 fillableTick1 = _fillableTick(isCurrency0, extremeTicks[1], spacing);
180
+ assertTrue(_isTickInitialized(poolKeyHash, orderCoin, fillableTick0, spacing), "far tick 1 should be initialized");
181
+ assertTrue(_isTickInitialized(poolKeyHash, orderCoin, fillableTick1, spacing), "far tick 2 should be initialized");
176
182
  }
177
183
 
178
184
  function _fundAndApprove(address user, address token, uint256 amount) internal {
@@ -202,7 +202,6 @@ contract LimitOrderFillTest is BaseTest {
202
202
  bool isCurrency0 = Currency.unwrap(key.currency0) == address(creatorCoin);
203
203
  address orderCoin = _orderCoin(key, isCurrency0);
204
204
 
205
- vm.prank(users.factoryOwner);
206
205
  limitOrderBook.setMaxFillCount(2);
207
206
 
208
207
  (uint256[] memory orderSizes, int24[] memory orderTicks) = _buildDeterministicOrders(key, isCurrency0, 3, 20e18);
@@ -336,7 +335,6 @@ contract LimitOrderFillTest is BaseTest {
336
335
 
337
336
  // 3. Now DISABLE hook auto-fills
338
337
  uint256 originalMaxFillCount = limitOrderBook.getMaxFillCount();
339
- vm.prank(users.factoryOwner);
340
338
  limitOrderBook.setMaxFillCount(0);
341
339
 
342
340
  // 4. Move price past the order WITHOUT triggering hook fills
@@ -345,7 +343,6 @@ contract LimitOrderFillTest is BaseTest {
345
343
  _swapSomeCurrencyForCoin(ICoin(address(creatorCoin)), address(zoraToken), swapAmount, swapper);
346
344
 
347
345
  // 5. Restore original maxFillCount
348
- vm.prank(users.factoryOwner);
349
346
  limitOrderBook.setMaxFillCount(originalMaxFillCount);
350
347
 
351
348
  // 6. Verify order exists and check epoch
@@ -775,7 +772,6 @@ contract LimitOrderFillTest is BaseTest {
775
772
  address orderCoin = _orderCoin(key, isCurrency0);
776
773
 
777
774
  // Set max fill count to 2 so we can verify default is used
778
- vm.prank(users.factoryOwner);
779
775
  limitOrderBook.setMaxFillCount(2);
780
776
 
781
777
  // Create 5 orders that can be filled
@@ -814,6 +810,48 @@ contract LimitOrderFillTest is BaseTest {
814
810
  assertEq(fills.length, 2, "should fill default max count of 2 orders");
815
811
  }
816
812
 
813
+ /// @notice Tests fill() caps maxFillCount to configured default
814
+ function test_fill_maxFillCountExceedsDefault_isCapped() public {
815
+ PoolKey memory key = creatorCoin.getPoolKey();
816
+ bool isCurrency0 = Currency.unwrap(key.currency0) == address(creatorCoin);
817
+ address orderCoin = _orderCoin(key, isCurrency0);
818
+
819
+ // Set max fill count to 2
820
+ limitOrderBook.setMaxFillCount(2);
821
+
822
+ // Create 5 fillable orders
823
+ (uint256[] memory orderSizes, int24[] memory orderTicks) = _buildDeterministicOrders(key, isCurrency0, 5, 20e18);
824
+ uint256 totalSize;
825
+ for (uint256 i; i < orderSizes.length; ++i) {
826
+ totalSize += orderSizes[i];
827
+ }
828
+
829
+ if (orderCoin == address(0)) {
830
+ vm.deal(users.seller, totalSize);
831
+ } else {
832
+ deal(orderCoin, users.seller, totalSize);
833
+ vm.startPrank(users.seller);
834
+ IERC20(orderCoin).approve(address(limitOrderBook), totalSize);
835
+ vm.stopPrank();
836
+ }
837
+
838
+ vm.recordLogs();
839
+ vm.prank(users.seller);
840
+ limitOrderBook.create{value: orderCoin == address(0) ? totalSize : 0}(key, isCurrency0, orderSizes, orderTicks, users.seller);
841
+ CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
842
+
843
+ _movePriceBeyondTicksWithAutoFillDisabled(created);
844
+ (int24 startTick, int24 endTick) = _tickWindow(created, key);
845
+
846
+ // Call fill with maxFillCount = 100 (way above default of 2)
847
+ vm.recordLogs();
848
+ limitOrderBook.fill(key, isCurrency0, startTick, endTick, 100, address(0));
849
+ FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
850
+
851
+ // Should cap to 2 orders (the configured max)
852
+ assertEq(fills.length, 2, "should cap to configured max of 2 orders");
853
+ }
854
+
817
855
  /// @notice Tests batch fill with empty orderIds array (line 134)
818
856
  /// @dev This tests the branch: if (batch.orderIds.length != 0)
819
857
  function test_batchFill_emptyOrderIds_skips() public {