@zoralabs/limit-orders 0.2.0
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.
- package/.turbo/turbo-build$colon$js.log +85 -0
- package/AUDIT_NOTES.md +33 -0
- package/AUDIT_RFP.md +408 -0
- package/CHANGELOG.md +25 -0
- package/GAS_COMPARISON_RESULTS.md +194 -0
- package/LICENSE +21 -0
- package/README.md +650 -0
- package/SPEC.md +291 -0
- package/abis/BalanceDeltaLibrary.json +15 -0
- package/abis/BeforeSwapDeltaLibrary.json +15 -0
- package/abis/CurrencyLibrary.json +25 -0
- package/abis/CustomRevert.json +28 -0
- package/abis/IAllowanceTransfer.json +486 -0
- package/abis/IAuthority.json +31 -0
- package/abis/ICoin.json +1074 -0
- package/abis/IDeployedCoinVersionLookup.json +21 -0
- package/abis/IDopplerErrors.json +44 -0
- package/abis/IEIP712.json +15 -0
- package/abis/IERC1363.json +373 -0
- package/abis/IERC165.json +21 -0
- package/abis/IERC20.json +185 -0
- package/abis/IERC20Minimal.json +172 -0
- package/abis/IERC6909Claims.json +288 -0
- package/abis/IERC7572.json +21 -0
- package/abis/IExtsload.json +64 -0
- package/abis/IExttload.json +40 -0
- package/abis/IHasCoinType.json +15 -0
- package/abis/IHasPoolKey.json +42 -0
- package/abis/IHasRewardsRecipients.json +54 -0
- package/abis/IHasSwapPath.json +60 -0
- package/abis/IHasTotalSupplyForPositions.json +15 -0
- package/abis/IHooks.json +789 -0
- package/abis/IMsgSender.json +15 -0
- package/abis/IPoolManager.json +1286 -0
- package/abis/IProtocolFees.json +174 -0
- package/abis/ISupportsLimitOrderFill.json +15 -0
- package/abis/ISwapPathRouter.json +92 -0
- package/abis/ISwapRouter.json +219 -0
- package/abis/IUniswapV3SwapCallback.json +25 -0
- package/abis/IUpgradeableDestinationV4Hook.json +84 -0
- package/abis/IUpgradeableDestinationV4HookWithUpdateableFee.json +95 -0
- package/abis/IUpgradeableV4Hook.json +112 -0
- package/abis/IZoraHookRegistry.json +188 -0
- package/abis/IZoraLimitOrderBook.json +623 -0
- package/abis/IZoraLimitOrderBookCoinsInterface.json +67 -0
- package/abis/IZoraV4CoinHook.json +610 -0
- package/abis/Permit2Payments.json +7 -0
- package/abis/Position.json +7 -0
- package/abis/SafeCast.json +7 -0
- package/abis/SafeCast160.json +7 -0
- package/abis/SafeERC20.json +34 -0
- package/abis/SimpleAccessManaged.json +57 -0
- package/abis/SimpleAccessManager.json +351 -0
- package/abis/SqrtPriceMath.json +22 -0
- package/abis/StateLibrary.json +80 -0
- package/abis/SwapLimitOrders.json +22 -0
- package/abis/SwapWithLimitOrders.json +457 -0
- package/abis/TickBitmap.json +18 -0
- package/abis/TickMath.json +24 -0
- package/abis/V3ToV4SwapLib.json +28 -0
- package/abis/ZoraLimitOrderBook.json +771 -0
- package/cache/solidity-files-cache.json +1 -0
- package/dist/index.cjs +760 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +731 -0
- package/dist/index.js.map +1 -0
- package/dist/wagmiGenerated.d.ts +1012 -0
- package/dist/wagmiGenerated.d.ts.map +1 -0
- package/foundry.toml +29 -0
- package/gas_comparison.py +49 -0
- package/out/BalanceDelta.sol/BalanceDeltaLibrary.json +1 -0
- package/out/BeforeSwapDelta.sol/BeforeSwapDeltaLibrary.json +1 -0
- package/out/BitMath.sol/BitMath.json +1 -0
- package/out/BytesLib.sol/BytesLib.json +1 -0
- package/out/CoinCommon.sol/CoinCommon.json +1 -0
- package/out/CoinConfigurationVersions.sol/CoinConfigurationVersions.json +1 -0
- package/out/CoinConstants.sol/CoinConstants.json +1 -0
- package/out/Context.sol/Context.json +1 -0
- package/out/Currency.sol/CurrencyLibrary.json +1 -0
- package/out/CurrencyReserves.sol/CurrencyReserves.json +1 -0
- package/out/CustomRevert.sol/CustomRevert.json +1 -0
- package/out/DopplerMath.sol/DopplerMath.json +1 -0
- package/out/FixedPoint128.sol/FixedPoint128.json +1 -0
- package/out/FixedPoint96.sol/FixedPoint96.json +1 -0
- package/out/FullMath.sol/FullMath.json +1 -0
- package/out/IAllowanceTransfer.sol/IAllowanceTransfer.json +1 -0
- package/out/IAuthority.sol/IAuthority.json +1 -0
- package/out/ICoin.sol/ICoin.json +1 -0
- package/out/ICoin.sol/IHasCoinType.json +1 -0
- package/out/ICoin.sol/IHasPoolKey.json +1 -0
- package/out/ICoin.sol/IHasSwapPath.json +1 -0
- package/out/ICoin.sol/IHasTotalSupplyForPositions.json +1 -0
- package/out/IDeployedCoinVersionLookup.sol/IDeployedCoinVersionLookup.json +1 -0
- package/out/IDopplerErrors.sol/IDopplerErrors.json +1 -0
- package/out/IEIP712.sol/IEIP712.json +1 -0
- package/out/IERC1363.sol/IERC1363.json +1 -0
- package/out/IERC165.sol/IERC165.json +1 -0
- package/out/IERC20.sol/IERC20.json +1 -0
- package/out/IERC20Minimal.sol/IERC20Minimal.json +1 -0
- package/out/IERC6909Claims.sol/IERC6909Claims.json +1 -0
- package/out/IERC7572.sol/IERC7572.json +1 -0
- package/out/IExtsload.sol/IExtsload.json +1 -0
- package/out/IExttload.sol/IExttload.json +1 -0
- package/out/IHasRewardsRecipients.sol/IHasRewardsRecipients.json +1 -0
- package/out/IHooks.sol/IHooks.json +1 -0
- package/out/IMsgSender.sol/IMsgSender.json +1 -0
- package/out/IPoolManager.sol/IPoolManager.json +1 -0
- package/out/IProtocolFees.sol/IProtocolFees.json +1 -0
- package/out/ISupportsLimitOrderFill.sol/ISupportsLimitOrderFill.json +1 -0
- package/out/ISwapPathRouter.sol/ISwapPathRouter.json +1 -0
- package/out/ISwapRouter.sol/ISwapRouter.json +1 -0
- package/out/IUniswapV3SwapCallback.sol/IUniswapV3SwapCallback.json +1 -0
- package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4Hook.json +1 -0
- package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4HookWithUpdateableFee.json +1 -0
- package/out/IUpgradeableV4Hook.sol/IUpgradeableV4Hook.json +1 -0
- package/out/IZoraHookRegistry.sol/IZoraHookRegistry.json +1 -0
- package/out/IZoraLimitOrderBook.sol/IZoraLimitOrderBook.json +1 -0
- package/out/IZoraLimitOrderBookCoinsInterface.sol/IZoraLimitOrderBookCoinsInterface.json +1 -0
- package/out/IZoraV4CoinHook.sol/IZoraV4CoinHook.json +1 -0
- package/out/LimitOrderBitmap.sol/LimitOrderBitmap.json +1 -0
- package/out/LimitOrderCommon.sol/LimitOrderCommon.json +1 -0
- package/out/LimitOrderCreate.sol/LimitOrderCreate.json +1 -0
- package/out/LimitOrderFill.sol/LimitOrderFill.json +1 -0
- package/out/LimitOrderLiquidity.sol/LimitOrderLiquidity.json +1 -0
- package/out/LimitOrderQueues.sol/LimitOrderQueues.json +1 -0
- package/out/LimitOrderStorage.sol/LimitOrderStorage.json +1 -0
- package/out/LimitOrderTypes.sol/LimitOrderTypes.json +1 -0
- package/out/LimitOrderWithdraw.sol/LimitOrderWithdraw.json +1 -0
- package/out/LiquidityAmounts.sol/LiquidityAmounts.json +1 -0
- package/out/LiquidityMath.sol/LiquidityMath.json +1 -0
- package/out/Lock.sol/Lock.json +1 -0
- package/out/NonzeroDeltaCount.sol/NonzeroDeltaCount.json +1 -0
- package/out/Path.sol/Path.json +1 -0
- package/out/PathKey.sol/PathKeyLibrary.json +1 -0
- package/out/Permit2Payments.sol/Permit2Payments.json +1 -0
- package/out/PoolId.sol/PoolIdLibrary.json +1 -0
- package/out/Position.sol/Position.json +1 -0
- package/out/SafeCast.sol/SafeCast.json +1 -0
- package/out/SafeCast160.sol/SafeCast160.json +1 -0
- package/out/SafeERC20.sol/SafeERC20.json +1 -0
- package/out/SimpleAccessManaged.sol/SimpleAccessManaged.json +1 -0
- package/out/SimpleAccessManager.sol/SimpleAccessManager.json +1 -0
- package/out/SqrtPriceMath.sol/SqrtPriceMath.json +1 -0
- package/out/StateLibrary.sol/StateLibrary.json +1 -0
- package/out/SwapLimitOrders.sol/SwapLimitOrders.json +1 -0
- package/out/SwapWithLimitOrders.sol/SwapWithLimitOrders.json +1 -0
- package/out/TickBitmap.sol/TickBitmap.json +1 -0
- package/out/TickMath.sol/TickMath.json +1 -0
- package/out/TransientSlot.sol/TransientSlot.json +1 -0
- package/out/TransientStateLibrary.sol/TransientStateLibrary.json +1 -0
- package/out/UniV4SwapToCurrency.sol/UniV4SwapToCurrency.json +1 -0
- package/out/UnsafeMath.sol/UnsafeMath.json +1 -0
- package/out/V3ToV4SwapLib.sol/V3ToV4SwapLib.json +1 -0
- package/out/ZoraLimitOrderBook.sol/ZoraLimitOrderBook.json +1 -0
- package/out/build-info/69718f10d1dc37f0.json +1 -0
- package/out/uniswap/BitMath.sol/BitMath.json +1 -0
- package/out/uniswap/CustomRevert.sol/CustomRevert.json +1 -0
- package/out/uniswap/FullMath.sol/FullMath.json +1 -0
- package/out/uniswap/SafeCast.sol/SafeCast.json +1 -0
- package/out/uniswap/TickMath.sol/TickMath.json +1 -0
- package/package/index.ts +1 -0
- package/package/wagmiGenerated.ts +738 -0
- package/package.json +57 -0
- package/remappings.txt +11 -0
- package/src/IZoraLimitOrderBook.sol +195 -0
- package/src/ZoraLimitOrderBook.sol +220 -0
- package/src/access/SimpleAccessManaged.sol +76 -0
- package/src/access/SimpleAccessManager.sol +268 -0
- package/src/libs/LimitOrderBitmap.sol +84 -0
- package/src/libs/LimitOrderCommon.sol +91 -0
- package/src/libs/LimitOrderCreate.sol +277 -0
- package/src/libs/LimitOrderFill.sol +362 -0
- package/src/libs/LimitOrderLiquidity.sol +222 -0
- package/src/libs/LimitOrderQueues.sol +101 -0
- package/src/libs/LimitOrderStorage.sol +34 -0
- package/src/libs/LimitOrderTypes.sol +41 -0
- package/src/libs/LimitOrderWithdraw.sol +100 -0
- package/src/libs/Permit2Payments.sol +41 -0
- package/src/libs/SwapLimitOrders.sol +209 -0
- package/src/router/SwapWithLimitOrders.sol +454 -0
- package/test/LimitOrderAccessControl.t.sol +461 -0
- package/test/LimitOrderBitmap.t.sol +194 -0
- package/test/LimitOrderCreate.t.sol +348 -0
- package/test/LimitOrderFill.t.sol +1005 -0
- package/test/LimitOrderLibraries.t.sol +354 -0
- package/test/LimitOrderLiquidityPayouts.t.sol +333 -0
- package/test/LimitOrderV4Pools.t.sol +157 -0
- package/test/LimitOrderWithdraw.t.sol +653 -0
- package/test/SimpleAccessManager.t.sol +420 -0
- package/test/SwapWithLimitOrders.t.sol +107 -0
- package/test/SwapWithLimitOrdersRouter.t.sol +1073 -0
- package/test/gas/LimitOrderFillGas.t.sol +1008 -0
- package/test/gas/LimitOrderSwapGas.t.sol +403 -0
- package/test/gas/logs/gas_benchmarks_fill_20251201.log +30 -0
- package/test/gas/logs/gas_benchmarks_swap_20251201.log +27 -0
- package/test/unit/LimitOrderBitmapUnit.t.sol +276 -0
- package/test/unit/LimitOrderCreateUnit.t.sol +358 -0
- package/test/unit/SwapLimitOrdersUnit.t.sol +672 -0
- package/test/unit/SwapLimitOrdersValidation.t.sol +423 -0
- package/test/unit/SwapWithLimitOrdersUnit.t.sol +321 -0
- package/test/utils/BaseTest.sol +793 -0
- package/test/utils/TestableZoraLimitOrderBook.sol +54 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +11 -0
- package/wagmi.config.ts +18 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import {BaseTest} from "./utils/BaseTest.sol";
|
|
5
|
+
import {LimitOrderCommon} from "../src/libs/LimitOrderCommon.sol";
|
|
6
|
+
import {CoinCommon} from "@zoralabs/coins/src/libs/CoinCommon.sol";
|
|
7
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
8
|
+
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
9
|
+
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
|
|
10
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
11
|
+
|
|
12
|
+
contract LimitOrderV4PoolsTest is BaseTest {
|
|
13
|
+
uint160 private constant INIT_SQRT_PRICE = 79228162514264337593543950336;
|
|
14
|
+
|
|
15
|
+
function _buildPoolKey(address token0, address token1, IHooks hooks) internal pure returns (PoolKey memory) {
|
|
16
|
+
bool isToken0Lower = token0 < token1;
|
|
17
|
+
|
|
18
|
+
return
|
|
19
|
+
PoolKey({
|
|
20
|
+
currency0: Currency.wrap(isToken0Lower ? token0 : token1),
|
|
21
|
+
currency1: Currency.wrap(isToken0Lower ? token1 : token0),
|
|
22
|
+
fee: 10_000,
|
|
23
|
+
tickSpacing: 200,
|
|
24
|
+
hooks: hooks
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function test_existingPool_stillWorksAfterRemovingHookCheck() public {
|
|
29
|
+
PoolKey memory key = creatorCoin.getPoolKey();
|
|
30
|
+
bool isCurrency0 = Currency.unwrap(key.currency0) == address(creatorCoin);
|
|
31
|
+
address orderCoin = isCurrency0 ? address(creatorCoin) : Currency.unwrap(key.currency1);
|
|
32
|
+
|
|
33
|
+
uint256[] memory orderSizes = new uint256[](1);
|
|
34
|
+
orderSizes[0] = 50e18;
|
|
35
|
+
|
|
36
|
+
int24 baseTick = _currentTick(key);
|
|
37
|
+
baseTick = _alignedTick(baseTick, key.tickSpacing);
|
|
38
|
+
|
|
39
|
+
int24[] memory orderTicks = new int24[](1);
|
|
40
|
+
orderTicks[0] = _alignedTickForOrder(isCurrency0, baseTick, key.tickSpacing, 0);
|
|
41
|
+
|
|
42
|
+
// Fund maker
|
|
43
|
+
deal(orderCoin, users.seller, orderSizes[0]);
|
|
44
|
+
vm.startPrank(users.seller);
|
|
45
|
+
IERC20(orderCoin).approve(address(limitOrderBook), orderSizes[0]);
|
|
46
|
+
vm.stopPrank();
|
|
47
|
+
|
|
48
|
+
// Create order
|
|
49
|
+
vm.prank(users.seller);
|
|
50
|
+
bytes32[] memory orderIds = limitOrderBook.create(key, isCurrency0, orderSizes, orderTicks, users.seller);
|
|
51
|
+
|
|
52
|
+
assertEq(orderIds.length, 1, "order not created");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function test_existingPool_fillWorksAfterPriceMovement() public {
|
|
56
|
+
PoolKey memory key = creatorCoin.getPoolKey();
|
|
57
|
+
bool isCurrency0 = Currency.unwrap(key.currency0) == address(creatorCoin);
|
|
58
|
+
address orderCoin = LimitOrderCommon.getOrderCoin(key, isCurrency0);
|
|
59
|
+
|
|
60
|
+
// Build orders
|
|
61
|
+
(uint256[] memory orderSizes, int24[] memory orderTicks) = _buildDeterministicOrders(key, isCurrency0, 2, 25e18);
|
|
62
|
+
uint256 totalSize = orderSizes[0] + orderSizes[1];
|
|
63
|
+
|
|
64
|
+
// Fund maker
|
|
65
|
+
deal(orderCoin, users.seller, totalSize);
|
|
66
|
+
vm.startPrank(users.seller);
|
|
67
|
+
IERC20(orderCoin).approve(address(limitOrderBook), totalSize);
|
|
68
|
+
vm.stopPrank();
|
|
69
|
+
|
|
70
|
+
// Create orders
|
|
71
|
+
vm.recordLogs();
|
|
72
|
+
vm.prank(users.seller);
|
|
73
|
+
limitOrderBook.create(key, isCurrency0, orderSizes, orderTicks, users.seller);
|
|
74
|
+
CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
|
|
75
|
+
|
|
76
|
+
assertEq(created.length, 2, "expected 2 orders created");
|
|
77
|
+
uint256 realizedSize = _sumOrderSizes(created);
|
|
78
|
+
assertEq(_makerBalance(users.seller, orderCoin), realizedSize, "maker balance mismatch after create");
|
|
79
|
+
|
|
80
|
+
uint256 previousMax = limitOrderBook.getMaxFillCount();
|
|
81
|
+
limitOrderBook.setMaxFillCount(0); // Disable autofill
|
|
82
|
+
|
|
83
|
+
address mover = makeAddr("price-mover");
|
|
84
|
+
uint128 swapAmount = uint128(DEFAULT_LIMIT_ORDER_AMOUNT * 10);
|
|
85
|
+
deal(address(zoraToken), mover, uint256(swapAmount));
|
|
86
|
+
_swapSomeCurrencyForCoin(creatorCoin, address(zoraToken), swapAmount, mover);
|
|
87
|
+
|
|
88
|
+
limitOrderBook.setMaxFillCount(previousMax); // Re-enable autofill
|
|
89
|
+
|
|
90
|
+
// Get tick window for fills
|
|
91
|
+
(int24 startTick, int24 endTick) = _tickWindow(created, key);
|
|
92
|
+
bytes32 poolKeyHash = CoinCommon.hashPoolKey(key);
|
|
93
|
+
uint256 epochBefore = _poolEpoch(poolKeyHash);
|
|
94
|
+
|
|
95
|
+
// Fill orders
|
|
96
|
+
vm.recordLogs();
|
|
97
|
+
limitOrderBook.fill(key, isCurrency0, startTick, endTick, created.length, address(0));
|
|
98
|
+
FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
|
|
99
|
+
|
|
100
|
+
// Verify fills
|
|
101
|
+
assertEq(fills.length, created.length, "fill count mismatch");
|
|
102
|
+
for (uint256 i; i < fills.length; ++i) {
|
|
103
|
+
assertEq(fills[i].maker, users.seller, "maker mismatch");
|
|
104
|
+
assertEq(fills[i].coinIn, orderCoin, "coin mismatch");
|
|
105
|
+
assertEq(fills[i].fillReferral, address(0), "unexpected referral");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Verify cleanup
|
|
109
|
+
assertEq(_makerBalance(users.seller, orderCoin), 0, "maker balance should be zero after fill");
|
|
110
|
+
assertGt(_poolEpoch(poolKeyHash), epochBefore, "pool epoch should increment");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function test_arbitraryPool_fillWithReferral() public {
|
|
114
|
+
PoolKey memory key = creatorCoin.getPoolKey();
|
|
115
|
+
bool isCurrency0 = Currency.unwrap(key.currency0) == address(creatorCoin);
|
|
116
|
+
address orderCoin = LimitOrderCommon.getOrderCoin(key, isCurrency0);
|
|
117
|
+
|
|
118
|
+
// Build single order
|
|
119
|
+
(uint256[] memory orderSizes, int24[] memory orderTicks) = _buildDeterministicOrders(key, isCurrency0, 1, 30e18);
|
|
120
|
+
|
|
121
|
+
// Fund maker
|
|
122
|
+
deal(orderCoin, users.seller, orderSizes[0]);
|
|
123
|
+
vm.startPrank(users.seller);
|
|
124
|
+
IERC20(orderCoin).approve(address(limitOrderBook), orderSizes[0]);
|
|
125
|
+
vm.stopPrank();
|
|
126
|
+
|
|
127
|
+
// Create order
|
|
128
|
+
vm.recordLogs();
|
|
129
|
+
vm.prank(users.seller);
|
|
130
|
+
limitOrderBook.create(key, isCurrency0, orderSizes, orderTicks, users.seller);
|
|
131
|
+
CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
|
|
132
|
+
|
|
133
|
+
// Move price using disable/enable pattern
|
|
134
|
+
uint256 previousMax = limitOrderBook.getMaxFillCount();
|
|
135
|
+
limitOrderBook.setMaxFillCount(0);
|
|
136
|
+
|
|
137
|
+
address mover = makeAddr("price-mover");
|
|
138
|
+
uint128 swapAmount = uint128(DEFAULT_LIMIT_ORDER_AMOUNT * 10);
|
|
139
|
+
deal(address(zoraToken), mover, uint256(swapAmount));
|
|
140
|
+
_swapSomeCurrencyForCoin(creatorCoin, address(zoraToken), swapAmount, mover);
|
|
141
|
+
|
|
142
|
+
limitOrderBook.setMaxFillCount(previousMax);
|
|
143
|
+
|
|
144
|
+
// Fill with referral
|
|
145
|
+
address referral = makeAddr("fillReferral");
|
|
146
|
+
(int24 startTick, int24 endTick) = _tickWindow(created, key);
|
|
147
|
+
|
|
148
|
+
vm.recordLogs();
|
|
149
|
+
limitOrderBook.fill(key, isCurrency0, startTick, endTick, created.length, referral);
|
|
150
|
+
FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
|
|
151
|
+
|
|
152
|
+
// Verify referral received fees
|
|
153
|
+
assertEq(fills.length, 1, "expected 1 fill");
|
|
154
|
+
assertEq(fills[0].fillReferral, referral, "referral address mismatch");
|
|
155
|
+
assertGt(fills[0].fillReferralAmount, 0, "referral should receive fees");
|
|
156
|
+
}
|
|
157
|
+
}
|