@paimaexample/evm-contracts 0.3.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.
Files changed (79) hide show
  1. package/README.md +7 -0
  2. package/deno.json +28 -0
  3. package/docs/templates/contract.hbs +144 -0
  4. package/docs/templates/helpers.js +61 -0
  5. package/docs/templates/page.hbs +7 -0
  6. package/docs/templates/properties.js +76 -0
  7. package/hardhat.config.ts +11 -0
  8. package/index.js +1 -0
  9. package/mod.ts +0 -0
  10. package/package.json +13 -0
  11. package/remappings.txt +1 -0
  12. package/src/companions/ERC165Contract.json +21 -0
  13. package/src/companions/ERC165Contract.ts +21 -0
  14. package/src/companions/ERC20Contract.json +222 -0
  15. package/src/companions/ERC20Contract.ts +222 -0
  16. package/src/companions/ERC6551RegistryContract.json +128 -0
  17. package/src/companions/ERC6551RegistryContract.ts +128 -0
  18. package/src/companions/ERC721Contract.json +248 -0
  19. package/src/companions/ERC721Contract.ts +222 -0
  20. package/src/companions/IERC1155Contract.json +295 -0
  21. package/src/companions/IERC1155Contract.ts +295 -0
  22. package/src/companions/OldERC6551RegistryContract.json +133 -0
  23. package/src/companions/OldERC6551RegistryContract.ts +133 -0
  24. package/src/companions/PaimaERC721Contract.json +787 -0
  25. package/src/companions/PaimaERC721Contract.ts +787 -0
  26. package/src/companions/PaimaL2Contract.json +134 -0
  27. package/src/companions/PaimaL2Contract.ts +134 -0
  28. package/src/companions/README.md +5 -0
  29. package/src/contracts/AnnotatedMintNft.sol +171 -0
  30. package/src/contracts/BaseState.sol +16 -0
  31. package/src/contracts/ERC1967.sol +43 -0
  32. package/src/contracts/Erc20NftSale.sol +186 -0
  33. package/src/contracts/GenericPayment.sol +60 -0
  34. package/src/contracts/NativeNftSale.sol +97 -0
  35. package/src/contracts/PaimaL2Contract.sol +54 -0
  36. package/src/contracts/Proxy/Erc20NftSaleProxy.sol +79 -0
  37. package/src/contracts/Proxy/GenericPaymentProxy.sol +64 -0
  38. package/src/contracts/Proxy/NativeNftSaleProxy.sol +72 -0
  39. package/src/contracts/Proxy/OrderbookDexProxy.sol +27 -0
  40. package/src/contracts/README.md +72 -0
  41. package/src/contracts/State.sol +25 -0
  42. package/src/contracts/dev/ERC721Dev.sol +13 -0
  43. package/src/contracts/dev/Erc20Dev.sol +13 -0
  44. package/src/contracts/dev/NativeNftSaleUpgradeDev.sol +9 -0
  45. package/src/contracts/dev/NftSaleUpgradeDev.sol +12 -0
  46. package/src/contracts/dev/NftTypeMapper.sol +38 -0
  47. package/src/contracts/dev/Token.sol +15 -0
  48. package/src/contracts/dev/UpgradeDev.sol +10 -0
  49. package/src/contracts/orderbook/IOrderbookDex.sol +215 -0
  50. package/src/contracts/orderbook/OrderbookDex.sol +435 -0
  51. package/src/contracts/token/IERC4906Agnostic.sol +17 -0
  52. package/src/contracts/token/IInverseAppProjected1155.sol +40 -0
  53. package/src/contracts/token/IInverseAppProjectedNft.sol +38 -0
  54. package/src/contracts/token/IInverseBaseProjected1155.sol +25 -0
  55. package/src/contracts/token/IInverseBaseProjectedNft.sol +29 -0
  56. package/src/contracts/token/IInverseProjected1155.sol +38 -0
  57. package/src/contracts/token/IInverseProjectedNft.sol +41 -0
  58. package/src/contracts/token/ITokenUri.sol +10 -0
  59. package/src/contracts/token/IUri.sol +13 -0
  60. package/src/contracts/token/InverseAppProjected1155.sol +218 -0
  61. package/src/contracts/token/InverseAppProjectedNft.sol +192 -0
  62. package/src/contracts/token/InverseBaseProjected1155.sol +170 -0
  63. package/src/contracts/token/InverseBaseProjectedNft.sol +158 -0
  64. package/src/plugin/common.ts +35 -0
  65. package/src/plugin/deployment.ts +161 -0
  66. package/src/plugin/mod.ts +6 -0
  67. package/src/plugin/paimaL2.ts +202 -0
  68. package/src/recommendedHardhat.ts +86 -0
  69. package/test/lib/StdInvariant.sol +96 -0
  70. package/test/lib/cheatcodes.sol +89 -0
  71. package/test/lib/console.sol +1884 -0
  72. package/test/lib/ctest.sol +678 -0
  73. package/test/src/InverseAppProjected1155.t.sol +207 -0
  74. package/test/src/InverseAppProjectedNft.t.sol +164 -0
  75. package/test/src/InverseBaseProjected1155.t.sol +171 -0
  76. package/test/src/InverseBaseProjectedNft.t.sol +141 -0
  77. package/test/src/OrderbookDex.t.sol +710 -0
  78. package/test/src/OrderbookDexInvariant.t.sol +426 -0
  79. package/test/src/PaimaL2ContractTest.sol +115 -0
@@ -0,0 +1,710 @@
1
+ // SPDX-License-Identifier: UNLICENSED
2
+ pragma solidity ^0.8.18;
3
+
4
+ import {CheatCodes} from "../lib/cheatcodes.sol";
5
+ import {CTest} from "../lib/ctest.sol";
6
+ import "../lib/console.sol";
7
+
8
+ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
9
+ import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
10
+ import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
11
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
12
+
13
+ import {IInverseAppProjected1155} from "../../src/contracts/token/IInverseAppProjected1155.sol";
14
+ import {InverseAppProjected1155} from "../../src/contracts/token/InverseAppProjected1155.sol";
15
+ import {IOrderbookDex} from "../../src/contracts/orderbook/IOrderbookDex.sol";
16
+ import {OrderbookDex} from "../../src/contracts/orderbook/OrderbookDex.sol";
17
+ import {OrderbookDexProxy} from "../../src/contracts/Proxy/OrderbookDexProxy.sol";
18
+
19
+ contract OrderbookDexTest is CTest, ERC1155Holder {
20
+ using Address for address payable;
21
+
22
+ CheatCodes vm = CheatCodes(HEVM_ADDRESS);
23
+ OrderbookDex public dex;
24
+ IInverseAppProjected1155 asset;
25
+ uint256 makerFee = 40;
26
+ uint256 takerFee = 60;
27
+ uint256 orderCreationFee = 10000 gwei;
28
+ address alice = vm.addr(uint256(keccak256(abi.encodePacked("alice"))));
29
+ address boris = vm.addr(uint256(keccak256(abi.encodePacked("boris"))));
30
+
31
+ function setUp() public {
32
+ asset = new InverseAppProjected1155("Gold", "GOLD", address(this));
33
+ dex = OrderbookDex(
34
+ address(
35
+ new OrderbookDexProxy(
36
+ address(new OrderbookDex()),
37
+ address(this),
38
+ makerFee,
39
+ takerFee,
40
+ orderCreationFee
41
+ )
42
+ )
43
+ );
44
+ asset.setApprovalForAll(address(dex), true);
45
+ vm.deal(alice, 1_000 ether);
46
+ vm.deal(boris, 1_000 ether);
47
+ }
48
+
49
+ function testFuzz_createSellOrder_satisfiesRequirements(
50
+ uint256 assetAmount,
51
+ uint256 pricePerAsset
52
+ ) public {
53
+ vm.assume(assetAmount > 0 && pricePerAsset > 0);
54
+
55
+ uint256 orderId = dex.currentOrderId(address(asset));
56
+ uint256 assetId = asset.mint(assetAmount, "");
57
+
58
+ vm.expectEmit(true, true, true, true);
59
+ emit IOrderbookDex.OrderCreated(
60
+ address(asset),
61
+ assetId,
62
+ orderId,
63
+ address(this),
64
+ assetAmount,
65
+ pricePerAsset,
66
+ makerFee,
67
+ takerFee
68
+ );
69
+ dex.createSellOrder{value: orderCreationFee}(
70
+ address(asset),
71
+ assetId,
72
+ assetAmount,
73
+ pricePerAsset
74
+ );
75
+
76
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderId);
77
+ assertEq(order.assetId, assetId);
78
+ assertEq(order.assetAmount, assetAmount);
79
+ assertEq(order.pricePerAsset, pricePerAsset);
80
+ assertEq(asset.balanceOf(address(dex), assetId), assetAmount);
81
+ }
82
+
83
+ function testFuzz_createBatchSellOrder_satisfiesRequirements(uint256 orderCount) public {
84
+ orderCount = bound(orderCount, 0, 100);
85
+ uint256[] memory assetIds = new uint256[](orderCount);
86
+ uint256[] memory assetAmounts = new uint256[](orderCount);
87
+ uint256[] memory pricesPerAssets = new uint256[](orderCount);
88
+
89
+ for (uint256 i = 0; i < orderCount; ++i) {
90
+ assetAmounts[i] = i == 0 ? 1 : i;
91
+ pricesPerAssets[i] = i == 0 ? 1 : i;
92
+ assetIds[i] = asset.mint(assetAmounts[i], "");
93
+ }
94
+
95
+ uint256 orderId = dex.currentOrderId(address(asset));
96
+ for (uint256 i = 0; i < orderCount; ++i) {
97
+ vm.expectEmit(true, true, true, true);
98
+ emit IOrderbookDex.OrderCreated(
99
+ address(asset),
100
+ assetIds[i],
101
+ orderId + i,
102
+ address(this),
103
+ assetAmounts[i],
104
+ pricesPerAssets[i],
105
+ makerFee,
106
+ takerFee
107
+ );
108
+ }
109
+
110
+ uint256[] memory orderIds = dex.createBatchSellOrder{
111
+ value: orderCreationFee * assetIds.length
112
+ }(address(asset), assetIds, assetAmounts, pricesPerAssets);
113
+
114
+ for (uint256 i = 0; i < orderCount; ++i) {
115
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderIds[i]);
116
+ assertEq(order.assetId, assetIds[i]);
117
+ assertEq(order.assetAmount, assetAmounts[i]);
118
+ assertEq(order.pricePerAsset, pricesPerAssets[i]);
119
+ assertEq(asset.balanceOf(address(dex), assetIds[i]), assetAmounts[i]);
120
+ }
121
+ }
122
+
123
+ function testFuzz_createBatchSellOrder_reverts_ifInvalidArrayLength(
124
+ uint256 length1,
125
+ uint256 length2,
126
+ uint256 length3
127
+ ) public {
128
+ length1 = bound(length1, 0, 1000);
129
+ length2 = bound(length2, 0, 1000);
130
+ length3 = bound(length3, 0, 1000);
131
+ vm.assume(length1 != length2 || length2 != length3);
132
+ uint256[] memory assetIds = new uint256[](length1);
133
+ uint256[] memory assetAmounts = new uint256[](length2);
134
+ uint256[] memory pricesPerAssets = new uint256[](length3);
135
+
136
+ vm.expectRevert(OrderbookDex.InvalidArrayLength.selector);
137
+ dex.createBatchSellOrder{value: orderCreationFee * assetIds.length}(
138
+ address(asset),
139
+ assetIds,
140
+ assetAmounts,
141
+ pricesPerAssets
142
+ );
143
+ }
144
+
145
+ function testFuzz_createSellOrder_reverts_ifAssetAmountIsZero(uint256 pricePerAsset) public {
146
+ uint256 assetId = asset.mint(0, "");
147
+
148
+ vm.expectRevert(abi.encodeWithSelector(OrderbookDex.InvalidInput.selector, 0));
149
+ dex.createSellOrder{value: orderCreationFee}(address(asset), assetId, 0, pricePerAsset);
150
+ }
151
+
152
+ function testFuzz_fillOrdersExactEth_transfersCorrectly(uint256 price) public {
153
+ uint256 ordersCount = 5;
154
+ vm.assume(price / ordersCount > 0);
155
+ vm.assume(price < type(uint256).max / ordersCount / orderCreationFee);
156
+ uint256[] memory orderIds = new uint256[](ordersCount);
157
+ address payable[] memory sellers = new address payable[](ordersCount);
158
+ uint256[] memory assetIds = new uint256[](ordersCount);
159
+ uint256 totalAssetAmount;
160
+ address buyer = address(this);
161
+ for (uint256 i = 0; i < ordersCount; i++) {
162
+ address payable seller = payable(vm.addr(uint256(keccak256(abi.encodePacked(i)))));
163
+ vm.deal(seller, orderCreationFee);
164
+ uint256 assetAmount = price / (ordersCount - i);
165
+ uint256 pricePerAsset = price / assetAmount;
166
+ orderIds[i] = dex.currentOrderId(address(asset));
167
+ sellers[i] = seller;
168
+ vm.startPrank(seller);
169
+ assetIds[i] = asset.mint(assetAmount, "");
170
+ asset.setApprovalForAll(address(dex), true);
171
+ dex.createSellOrder{value: orderCreationFee}(
172
+ address(asset),
173
+ assetIds[i],
174
+ assetAmount,
175
+ pricePerAsset
176
+ );
177
+ totalAssetAmount += assetAmount;
178
+ }
179
+
180
+ {
181
+ vm.deal(buyer, type(uint256).max);
182
+ uint256 buyerBalanceBefore = buyer.balance;
183
+ uint256[] memory sellersBalancesBefore = new uint256[](ordersCount);
184
+ uint256[] memory expectedPayouts = new uint256[](ordersCount);
185
+ uint256[] memory ordersAssetAmounts = new uint256[](ordersCount);
186
+ uint256 totalExpectedPayout;
187
+ for (uint256 i = 0; i < ordersCount; i++) {
188
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderIds[i]);
189
+ expectedPayouts[i] = order.pricePerAsset * order.assetAmount;
190
+ ordersAssetAmounts[i] = order.assetAmount;
191
+ totalExpectedPayout += expectedPayouts[i];
192
+ sellersBalancesBefore[i] = dex.balances(sellers[i]);
193
+ vm.expectEmit(true, true, true, true);
194
+ emit IOrderbookDex.OrderFilled(
195
+ address(asset),
196
+ assetIds[i],
197
+ orderIds[i],
198
+ sellers[i],
199
+ buyer,
200
+ order.assetAmount,
201
+ order.pricePerAsset,
202
+ (expectedPayouts[i] * makerFee) / 10000,
203
+ (expectedPayouts[i] * takerFee) / 10000
204
+ );
205
+ }
206
+ vm.startPrank(buyer);
207
+ dex.fillOrdersExactEth{
208
+ value: totalExpectedPayout + (totalExpectedPayout * takerFee) / 10000 + 1000
209
+ }(address(asset), totalAssetAmount, orderIds);
210
+
211
+ {
212
+ uint256 takerFeeTotal;
213
+ for (uint256 i = 0; i < ordersCount; i++) {
214
+ takerFeeTotal += (expectedPayouts[i] * takerFee) / 10000;
215
+ }
216
+ assertEq(buyer.balance, buyerBalanceBefore - (totalExpectedPayout + takerFeeTotal));
217
+ }
218
+ for (uint256 i = 0; i < ordersCount; i++) {
219
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderIds[i]);
220
+ assertEq(
221
+ dex.balances(sellers[i]),
222
+ sellersBalancesBefore[i] +
223
+ expectedPayouts[i] -
224
+ ((expectedPayouts[i] * makerFee) / 10000)
225
+ );
226
+ assertEq(order.assetAmount, 0);
227
+ assertEq(asset.balanceOf(address(dex), order.assetId), 0);
228
+ assertEq(asset.balanceOf(buyer, order.assetId), ordersAssetAmounts[i]);
229
+ }
230
+
231
+ for (uint256 i = 0; i < ordersCount; i++) {
232
+ uint256 sellerBalancesBefore = sellers[i].balance;
233
+ uint256 sellerBalanceInDex = dex.balances(sellers[i]);
234
+ vm.startPrank(sellers[i]);
235
+ dex.claim();
236
+ assertEq(sellers[i].balance, sellerBalancesBefore + sellerBalanceInDex);
237
+ }
238
+ }
239
+ }
240
+
241
+ function testFuzz_fillOrdersExactAsset_transfersCorrectly(uint256 price) public {
242
+ uint256 ordersCount = 5;
243
+ vm.assume(price / ordersCount > 0);
244
+ vm.assume(price < type(uint256).max / ordersCount / orderCreationFee);
245
+ uint256[] memory orderIds = new uint256[](ordersCount);
246
+ address payable[] memory sellers = new address payable[](ordersCount);
247
+ uint256[] memory assetIds = new uint256[](ordersCount);
248
+ uint256 totalAssetAmount;
249
+ address buyer = address(this);
250
+ for (uint256 i = 0; i < ordersCount; i++) {
251
+ address payable seller = payable(vm.addr(uint256(keccak256(abi.encodePacked(i)))));
252
+ vm.deal(seller, orderCreationFee);
253
+ uint256 assetAmount = price / (ordersCount - i);
254
+ uint256 pricePerAsset = price / assetAmount;
255
+ orderIds[i] = dex.currentOrderId(address(asset));
256
+ sellers[i] = seller;
257
+ vm.startPrank(seller);
258
+ assetIds[i] = asset.mint(assetAmount, "");
259
+ asset.setApprovalForAll(address(dex), true);
260
+ dex.createSellOrder{value: orderCreationFee}(
261
+ address(asset),
262
+ assetIds[i],
263
+ assetAmount,
264
+ pricePerAsset
265
+ );
266
+ totalAssetAmount += assetAmount;
267
+ }
268
+
269
+ {
270
+ vm.deal(buyer, type(uint256).max);
271
+ uint256 buyerBalanceBefore = buyer.balance;
272
+ uint256[] memory sellersBalancesBefore = new uint256[](ordersCount);
273
+ uint256[] memory expectedPayouts = new uint256[](ordersCount);
274
+ uint256[] memory ordersAssetAmounts = new uint256[](ordersCount);
275
+ uint256 totalExpectedPayout;
276
+ for (uint256 i = 0; i < ordersCount; i++) {
277
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderIds[i]);
278
+ expectedPayouts[i] = order.pricePerAsset * order.assetAmount;
279
+ ordersAssetAmounts[i] = order.assetAmount;
280
+ totalExpectedPayout += expectedPayouts[i];
281
+ sellersBalancesBefore[i] = dex.balances(sellers[i]);
282
+ vm.expectEmit(true, true, true, true);
283
+ emit IOrderbookDex.OrderFilled(
284
+ address(asset),
285
+ assetIds[i],
286
+ orderIds[i],
287
+ sellers[i],
288
+ buyer,
289
+ order.assetAmount,
290
+ order.pricePerAsset,
291
+ (expectedPayouts[i] * makerFee) / 10000,
292
+ (expectedPayouts[i] * takerFee) / 10000
293
+ );
294
+ }
295
+ vm.startPrank(buyer);
296
+ dex.fillOrdersExactAsset{
297
+ value: totalExpectedPayout + (totalExpectedPayout * takerFee) / 10000 + 1000
298
+ }(address(asset), totalAssetAmount, orderIds);
299
+
300
+ {
301
+ uint256 takerFeeTotal;
302
+ for (uint256 i = 0; i < ordersCount; i++) {
303
+ takerFeeTotal += (expectedPayouts[i] * takerFee) / 10000;
304
+ }
305
+ assertEq(buyer.balance, buyerBalanceBefore - (totalExpectedPayout + takerFeeTotal));
306
+ }
307
+ for (uint256 i = 0; i < ordersCount; i++) {
308
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderIds[i]);
309
+ assertEq(
310
+ dex.balances(sellers[i]),
311
+ sellersBalancesBefore[i] +
312
+ expectedPayouts[i] -
313
+ ((expectedPayouts[i] * makerFee) / 10000)
314
+ );
315
+ assertEq(order.assetAmount, 0);
316
+ assertEq(asset.balanceOf(address(dex), order.assetId), 0);
317
+ assertEq(asset.balanceOf(buyer, order.assetId), ordersAssetAmounts[i]);
318
+ }
319
+
320
+ for (uint256 i = 0; i < ordersCount; i++) {
321
+ uint256 sellerBalancesBefore = sellers[i].balance;
322
+ uint256 sellerBalanceInDex = dex.balances(sellers[i]);
323
+ vm.startPrank(sellers[i]);
324
+ dex.claim();
325
+ assertEq(sellers[i].balance, sellerBalancesBefore + sellerBalanceInDex);
326
+ }
327
+ }
328
+ }
329
+
330
+ function testFuzz_fillOrdersExactEth_transfersCorrectlyWithPartialFill(
331
+ uint256 assetAmount,
332
+ uint256 pricePerAsset,
333
+ uint256 assetAmountToBuy
334
+ ) public {
335
+ vm.assume(assetAmount > 0 && pricePerAsset > 1);
336
+ vm.assume(assetAmount < type(uint256).max / pricePerAsset / orderCreationFee);
337
+ vm.assume(pricePerAsset < type(uint256).max / assetAmount);
338
+ vm.assume(assetAmountToBuy < assetAmount);
339
+
340
+ address buyer = alice;
341
+ address payable seller = payable(address(this));
342
+ vm.deal(buyer, (assetAmount * pricePerAsset * (10000 + takerFee)) / 10000);
343
+ uint256 orderId = dex.currentOrderId(address(asset));
344
+ vm.startPrank(seller);
345
+ uint256 assetId = asset.mint(assetAmount, "");
346
+ asset.setApprovalForAll(address(dex), true);
347
+ dex.createSellOrder{value: orderCreationFee}(
348
+ address(asset),
349
+ assetId,
350
+ assetAmount,
351
+ pricePerAsset
352
+ );
353
+
354
+ uint256 buyerBalanceBefore = buyer.balance;
355
+ uint256 sellerBalanceBefore = dex.balances(seller);
356
+ uint256[] memory orderIds = new uint256[](1);
357
+ orderIds[0] = orderId;
358
+ uint256 purchaseCost = assetAmountToBuy * pricePerAsset;
359
+ vm.startPrank(buyer);
360
+ dex.fillOrdersExactEth{value: (purchaseCost + (purchaseCost * takerFee) / 10000)}(
361
+ address(asset),
362
+ assetAmountToBuy,
363
+ orderIds
364
+ );
365
+
366
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderId);
367
+ assertEq(
368
+ buyer.balance,
369
+ buyerBalanceBefore - (purchaseCost + (purchaseCost * takerFee) / 10000)
370
+ );
371
+ assertEq(
372
+ dex.balances(seller),
373
+ sellerBalanceBefore + (purchaseCost - (purchaseCost * makerFee) / 10000)
374
+ );
375
+ assertEq(order.assetAmount, assetAmount - assetAmountToBuy);
376
+ assertEq(asset.balanceOf(address(dex), assetId), assetAmount - assetAmountToBuy);
377
+ assertEq(asset.balanceOf(buyer, assetId), assetAmountToBuy);
378
+ }
379
+
380
+ function testFuzz_fillOrdersExactAsset_transfersCorrectlyWithPartialFill(
381
+ uint256 assetAmount,
382
+ uint256 pricePerAsset,
383
+ uint256 assetAmountToBuy
384
+ ) public {
385
+ vm.assume(assetAmount > 0 && pricePerAsset > 0);
386
+ vm.assume(assetAmount < type(uint256).max / pricePerAsset / orderCreationFee);
387
+ vm.assume(pricePerAsset < type(uint256).max / assetAmount);
388
+ vm.assume(assetAmountToBuy < assetAmount);
389
+
390
+ address buyer = alice;
391
+ address payable seller = payable(address(this));
392
+ vm.deal(buyer, (assetAmount * pricePerAsset * (10000 + takerFee)) / 10000);
393
+ uint256 orderId = dex.currentOrderId(address(asset));
394
+ vm.startPrank(seller);
395
+ uint256 assetId = asset.mint(assetAmount, "");
396
+ asset.setApprovalForAll(address(dex), true);
397
+ dex.createSellOrder{value: orderCreationFee}(
398
+ address(asset),
399
+ assetId,
400
+ assetAmount,
401
+ pricePerAsset
402
+ );
403
+
404
+ uint256 buyerBalanceBefore = buyer.balance;
405
+ uint256 sellerBalanceBefore = dex.balances(seller);
406
+ uint256[] memory orderIds = new uint256[](1);
407
+ orderIds[0] = orderId;
408
+ uint256 purchaseCost = assetAmountToBuy * pricePerAsset;
409
+ vm.startPrank(buyer);
410
+ dex.fillOrdersExactAsset{value: buyerBalanceBefore}(
411
+ address(asset),
412
+ assetAmountToBuy,
413
+ orderIds
414
+ );
415
+
416
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderId);
417
+ assertEq(
418
+ buyer.balance,
419
+ buyerBalanceBefore - (purchaseCost + (purchaseCost * takerFee) / 10000)
420
+ );
421
+ assertEq(
422
+ dex.balances(seller),
423
+ sellerBalanceBefore + (purchaseCost - (purchaseCost * makerFee) / 10000)
424
+ );
425
+ assertEq(order.assetAmount, assetAmount - assetAmountToBuy);
426
+ assertEq(asset.balanceOf(address(dex), assetId), assetAmount - assetAmountToBuy);
427
+ assertEq(asset.balanceOf(buyer, assetId), assetAmountToBuy);
428
+ }
429
+
430
+ function testFuzz_fillOrdersExactEth_refundsExcessValue(
431
+ uint256 assetAmount,
432
+ uint256 pricePerAsset
433
+ ) public {
434
+ uint256 multiplier = 3;
435
+ vm.assume(assetAmount > 0 && pricePerAsset > 0);
436
+ vm.assume(assetAmount < type(uint256).max / pricePerAsset / multiplier / orderCreationFee);
437
+ vm.assume(pricePerAsset < type(uint256).max / assetAmount / multiplier);
438
+
439
+ vm.deal(alice, assetAmount * pricePerAsset * multiplier);
440
+ uint256 orderId = dex.currentOrderId(address(asset));
441
+ uint256 assetId = asset.mint(assetAmount, "");
442
+ dex.createSellOrder{value: orderCreationFee}(
443
+ address(asset),
444
+ assetId,
445
+ assetAmount,
446
+ pricePerAsset
447
+ );
448
+
449
+ uint256 purchaseCost = assetAmount * pricePerAsset;
450
+ uint256 paidTakerFee = (purchaseCost * takerFee) / 10000;
451
+ uint256 aliceBalanceBefore = alice.balance;
452
+ uint256[] memory orderIds = new uint256[](1);
453
+ orderIds[0] = orderId;
454
+ vm.startPrank(alice);
455
+ dex.fillOrdersExactEth{value: alice.balance}(address(asset), assetAmount, orderIds);
456
+ assertEq(alice.balance, aliceBalanceBefore - (purchaseCost + paidTakerFee));
457
+ }
458
+
459
+ function testFuzz_fillOrdersExactAsset_refundsExcessValue(
460
+ uint256 assetAmount,
461
+ uint256 pricePerAsset
462
+ ) public {
463
+ uint256 multiplier = 3;
464
+ vm.assume(assetAmount > 0 && pricePerAsset > 0);
465
+ vm.assume(assetAmount < type(uint256).max / pricePerAsset / multiplier / orderCreationFee);
466
+ vm.assume(pricePerAsset < type(uint256).max / assetAmount / multiplier);
467
+
468
+ vm.deal(alice, assetAmount * pricePerAsset * multiplier);
469
+ uint256 orderId = dex.currentOrderId(address(asset));
470
+ uint256 assetId = asset.mint(assetAmount, "");
471
+ dex.createSellOrder{value: orderCreationFee}(
472
+ address(asset),
473
+ assetId,
474
+ assetAmount,
475
+ pricePerAsset
476
+ );
477
+
478
+ uint256 purchaseCost = assetAmount * pricePerAsset;
479
+ uint256 paidTakerFee = (purchaseCost * takerFee) / 10000;
480
+ uint256 aliceBalanceBefore = alice.balance;
481
+ uint256[] memory orderIds = new uint256[](1);
482
+ orderIds[0] = orderId;
483
+ vm.startPrank(alice);
484
+ dex.fillOrdersExactAsset{value: alice.balance}(address(asset), assetAmount, orderIds);
485
+ assertEq(alice.balance, aliceBalanceBefore - (purchaseCost + paidTakerFee));
486
+ }
487
+
488
+ function test_fillOrdersExactEth_wontFillOrderIfOrderWasCancelled() public {
489
+ uint256 assetAmount = 100;
490
+ uint256 pricePerAsset = 200;
491
+ uint256 orderId = dex.currentOrderId(address(asset));
492
+ uint256 assetId = asset.mint(assetAmount, "");
493
+ dex.createSellOrder{value: orderCreationFee}(
494
+ address(asset),
495
+ assetId,
496
+ assetAmount,
497
+ pricePerAsset
498
+ );
499
+ dex.cancelSellOrder(address(asset), orderId);
500
+
501
+ uint256 aliceBalanceBefore = alice.balance;
502
+ uint256[] memory orderIds = new uint256[](1);
503
+ orderIds[0] = orderId;
504
+ vm.startPrank(alice);
505
+
506
+ dex.fillOrdersExactEth{value: assetAmount * pricePerAsset}(address(asset), 0, orderIds);
507
+ assertEq(alice.balance, aliceBalanceBefore);
508
+ }
509
+
510
+ function test_fillOrdersExactAsset_wontFillOrderIfOrderWasCancelled() public {
511
+ uint256 assetAmount = 100;
512
+ uint256 pricePerAsset = 200;
513
+ uint256 orderId = dex.currentOrderId(address(asset));
514
+ uint256 assetId = asset.mint(assetAmount, "");
515
+ dex.createSellOrder{value: orderCreationFee}(
516
+ address(asset),
517
+ assetId,
518
+ assetAmount,
519
+ pricePerAsset
520
+ );
521
+ dex.cancelSellOrder(address(asset), orderId);
522
+
523
+ uint256 aliceBalanceBefore = alice.balance;
524
+ uint256[] memory orderIds = new uint256[](1);
525
+ orderIds[0] = orderId;
526
+ vm.startPrank(alice);
527
+
528
+ dex.fillOrdersExactAsset{value: assetAmount * pricePerAsset}(address(asset), 0, orderIds);
529
+ assertEq(alice.balance, aliceBalanceBefore);
530
+ }
531
+
532
+ function test_fillOrdersExactEth_reverts_ifInsufficientEndAmount() public {
533
+ uint256 assetAmount = 10;
534
+ uint256 pricePerAsset = 100;
535
+ uint256 orderId = dex.currentOrderId(address(asset));
536
+ uint256 assetId = asset.mint(assetAmount, "");
537
+ dex.createSellOrder{value: orderCreationFee}(
538
+ address(asset),
539
+ assetId,
540
+ assetAmount,
541
+ pricePerAsset
542
+ );
543
+
544
+ address payable[] memory sellers = new address payable[](1);
545
+ sellers[0] = payable(address(this));
546
+ uint256[] memory orderIds = new uint256[](1);
547
+ orderIds[0] = orderId;
548
+ uint256 purchaseCost = assetAmount * pricePerAsset;
549
+ uint256 paidTakerFee = (purchaseCost * takerFee) / 10000;
550
+ vm.startPrank(alice);
551
+ vm.expectRevert(
552
+ abi.encodeWithSelector(
553
+ OrderbookDex.InsufficientEndAmount.selector,
554
+ assetAmount + 1,
555
+ assetAmount
556
+ )
557
+ );
558
+ dex.fillOrdersExactEth{value: purchaseCost + paidTakerFee}(
559
+ address(asset),
560
+ assetAmount + 1,
561
+ orderIds
562
+ );
563
+ }
564
+
565
+ function test_fillOrdersExactAsset_reverts_ifInsufficientEndAmount() public {
566
+ uint256 assetAmount = 10;
567
+ uint256 pricePerAsset = 100;
568
+ uint256 orderId = dex.currentOrderId(address(asset));
569
+ uint256 assetId = asset.mint(assetAmount, "");
570
+ dex.createSellOrder{value: orderCreationFee}(
571
+ address(asset),
572
+ assetId,
573
+ assetAmount,
574
+ pricePerAsset
575
+ );
576
+
577
+ address payable[] memory sellers = new address payable[](1);
578
+ sellers[0] = payable(address(this));
579
+ uint256[] memory orderIds = new uint256[](1);
580
+ orderIds[0] = orderId;
581
+ uint256 purchaseCost = assetAmount * pricePerAsset;
582
+ uint256 paidTakerFee = (purchaseCost * takerFee) / 10000;
583
+ vm.startPrank(alice);
584
+ vm.expectRevert(
585
+ abi.encodeWithSelector(
586
+ OrderbookDex.InsufficientEndAmount.selector,
587
+ assetAmount + 1,
588
+ assetAmount
589
+ )
590
+ );
591
+ dex.fillOrdersExactAsset{value: purchaseCost + paidTakerFee}(
592
+ address(asset),
593
+ assetAmount + 1,
594
+ orderIds
595
+ );
596
+ }
597
+
598
+ function test_supportsInterface_returnsTrueForImplementedInterfaces() public {
599
+ assertTrue(dex.supportsInterface(type(IERC165).interfaceId));
600
+ assertTrue(dex.supportsInterface(type(IERC165).interfaceId));
601
+ assertTrue(dex.supportsInterface(type(IOrderbookDex).interfaceId));
602
+ }
603
+
604
+ function testFuzz_cancelSellOrder_satisfiesRequirements(uint256 assetAmount) public {
605
+ vm.assume(assetAmount > 0);
606
+ uint256 assetId = asset.mint(assetAmount, "");
607
+ uint256 orderId = dex.currentOrderId(address(asset));
608
+ dex.createSellOrder{value: orderCreationFee}(address(asset), assetId, assetAmount, 200);
609
+
610
+ vm.expectEmit(true, true, true, true);
611
+ emit IOrderbookDex.OrderCancelled(address(asset), assetId, orderId);
612
+ dex.cancelSellOrder(address(asset), orderId);
613
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderId);
614
+ assertEq(order.assetAmount, 0);
615
+ assertEq(asset.balanceOf(address(dex), assetId), 0);
616
+ assertEq(asset.balanceOf(address(this), assetId), assetAmount);
617
+ }
618
+
619
+ function test_cancelSellOrder_reverts_ifUnauthorized() public {
620
+ uint256 orderId = dex.currentOrderId(address(asset));
621
+ uint256 assetId = asset.mint(100, "");
622
+ dex.createSellOrder{value: orderCreationFee}(address(asset), assetId, 100, 200);
623
+
624
+ vm.startPrank(alice);
625
+ vm.expectRevert(abi.encodeWithSelector(OrderbookDex.Unauthorized.selector, alice));
626
+ dex.cancelSellOrder(address(asset), orderId);
627
+ }
628
+
629
+ function testFuzz_cancelBatchSellOrder_satisfiesRequirements(uint256 orderCount) public {
630
+ orderCount = bound(orderCount, 0, 100);
631
+ uint256[] memory assetIds = new uint256[](orderCount);
632
+ uint256[] memory assetAmounts = new uint256[](orderCount);
633
+ uint256[] memory pricesPerAssets = new uint256[](orderCount);
634
+
635
+ for (uint256 i = 0; i < orderCount; ++i) {
636
+ assetAmounts[i] = i == 0 ? 1 : i;
637
+ pricesPerAssets[i] = i == 0 ? 1 : i;
638
+ assetIds[i] = asset.mint(assetAmounts[i], "");
639
+ }
640
+
641
+ uint256 orderId = dex.currentOrderId(address(asset));
642
+
643
+ uint256[] memory orderIds = dex.createBatchSellOrder{
644
+ value: orderCreationFee * assetIds.length
645
+ }(address(asset), assetIds, assetAmounts, pricesPerAssets);
646
+
647
+ for (uint256 i = 0; i < orderCount; ++i) {
648
+ vm.expectEmit(true, true, true, true);
649
+ emit IOrderbookDex.OrderCancelled(address(asset), assetIds[i], orderId + i);
650
+ }
651
+
652
+ dex.cancelBatchSellOrder(address(asset), orderIds);
653
+
654
+ for (uint256 i = 0; i < orderCount; ++i) {
655
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderIds[i]);
656
+ assertEq(order.assetAmount, 0);
657
+ assertEq(asset.balanceOf(address(dex), assetIds[i]), 0);
658
+ assertEq(asset.balanceOf(address(this), assetIds[i]), assetAmounts[i]);
659
+ }
660
+ }
661
+
662
+ function test_cancelBatchSellOrder_reverts_ifUnauthorized() public {
663
+ uint256 orderId = dex.currentOrderId(address(asset));
664
+ uint256 assetId = asset.mint(100, "");
665
+ dex.createSellOrder{value: orderCreationFee}(address(asset), assetId, 100, 200);
666
+
667
+ uint256[] memory orderIds = new uint256[](1);
668
+ orderIds[0] = orderId;
669
+
670
+ vm.startPrank(alice);
671
+ vm.expectRevert(abi.encodeWithSelector(OrderbookDex.Unauthorized.selector, alice));
672
+ dex.cancelBatchSellOrder(address(asset), orderIds);
673
+ }
674
+
675
+ // Not really a test, was used to measure gas usage
676
+ function test_fillOrders_getGasUsage() public {
677
+ uint256 orderCount = 500;
678
+ uint256[] memory assetIds = new uint256[](orderCount);
679
+ uint256[] memory assetAmounts = new uint256[](orderCount);
680
+ uint256[] memory pricesPerAssets = new uint256[](orderCount);
681
+
682
+ uint256 totalPurchaseCost;
683
+ for (uint256 i = 0; i < orderCount; ++i) {
684
+ assetAmounts[i] = i + 1;
685
+ pricesPerAssets[i] = i + 1;
686
+ assetIds[i] = asset.mint(assetAmounts[i], "");
687
+ totalPurchaseCost += assetAmounts[i] * pricesPerAssets[i];
688
+ }
689
+ uint256[] memory orderIds = dex.createBatchSellOrder{
690
+ value: orderCreationFee * assetIds.length
691
+ }(address(asset), assetIds, assetAmounts, pricesPerAssets);
692
+ uint256 paidTakerFee = (totalPurchaseCost * takerFee) / 10000;
693
+
694
+ assertEq(orderIds.length, orderCount);
695
+
696
+ uint256 minimumAsset = 1;
697
+ uint256 value = type(uint256).max;
698
+ vm.deal(address(this), value);
699
+
700
+ uint256 startGas = gasleft();
701
+ dex.fillOrdersExactEth{value: totalPurchaseCost + paidTakerFee}(
702
+ address(asset),
703
+ minimumAsset,
704
+ orderIds
705
+ );
706
+ console.log("fill orders gas used", startGas - gasleft());
707
+ }
708
+
709
+ receive() external payable {}
710
+ }