@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,426 @@
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
+
7
+ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
8
+ import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
9
+ import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
10
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
11
+
12
+ import {IInverseAppProjected1155} from "../../src/contracts/token/IInverseAppProjected1155.sol";
13
+ import {InverseAppProjected1155} from "../../src/contracts/token/InverseAppProjected1155.sol";
14
+ import {IOrderbookDex} from "../../src/contracts/orderbook/IOrderbookDex.sol";
15
+ import {OrderbookDex} from "../../src/contracts/orderbook/OrderbookDex.sol";
16
+ import {OrderbookDexProxy} from "../../src/contracts/Proxy/OrderbookDexProxy.sol";
17
+
18
+ contract AssetHandler is CTest, ERC1155Holder {
19
+ CheatCodes vm = CheatCodes(HEVM_ADDRESS);
20
+ IOrderbookDex public dex;
21
+ IInverseAppProjected1155 asset;
22
+
23
+ constructor(IInverseAppProjected1155 _asset, IOrderbookDex _dex) {
24
+ asset = _asset;
25
+ dex = _dex;
26
+ }
27
+
28
+ function safeTransferFrom(
29
+ address from,
30
+ address to,
31
+ uint256 id,
32
+ uint256 value,
33
+ bytes calldata data
34
+ ) public {
35
+ vm.assume(asset.balanceOf(from, id) >= value);
36
+ address sender = msg.sender;
37
+ vm.prank(from);
38
+ asset.setApprovalForAll(sender, true);
39
+ vm.prank(sender);
40
+ asset.safeTransferFrom(from, to, id, value, data);
41
+ }
42
+
43
+ function safeBatchTransferFrom(
44
+ address from,
45
+ address to,
46
+ uint256[] calldata ids,
47
+ uint256[] calldata values,
48
+ bytes calldata data
49
+ ) public {
50
+ vm.assume(ids.length == values.length);
51
+ for (uint256 i; i < ids.length; ++i) {
52
+ vm.assume(asset.balanceOf(from, ids[i]) >= values[i]);
53
+ }
54
+ address sender = msg.sender;
55
+ vm.prank(from);
56
+ asset.setApprovalForAll(sender, true);
57
+ vm.prank(sender);
58
+ asset.safeBatchTransferFrom(from, to, ids, values, data);
59
+ }
60
+ }
61
+
62
+ contract OrderbookDexHandler is CTest, ERC1155Holder {
63
+ CheatCodes vm = CheatCodes(HEVM_ADDRESS);
64
+ IOrderbookDex public dex;
65
+ IInverseAppProjected1155 asset;
66
+
67
+ // Reasonable limits resulting in acceptable testing time
68
+ uint256 constant MAX_ASSET_AMOUNT = 100 ether;
69
+ uint256 constant MAX_PRICE = 1 ether;
70
+ uint256 constant MAX_BATCH_CREATE_SELL_ORDER = 50;
71
+ uint256 constant MAX_BATCH_CANCEL_SELL_ORDER = 10;
72
+ uint256 constant MAX_ORDER_FILL_LENGTH = 5;
73
+
74
+ // Helper variables
75
+ mapping(uint256 tokenId => uint256) buyerTokenBalanceBefore;
76
+ mapping(uint256 tokenId => uint256) expectedTokenBalanceGain;
77
+ mapping(uint256 orderId => bool) orderIdUsed;
78
+ uint256[] newOrderIds;
79
+ uint256 public previousOrderId;
80
+
81
+ address internal currentActor;
82
+
83
+ modifier useActor(uint256 actorIndexSeed) {
84
+ _useActor(actorIndexSeed);
85
+ _;
86
+ vm.stopPrank();
87
+ }
88
+
89
+ function _useActor(uint256 actorIndexSeed) internal {
90
+ uint256 actorIndex = bound(actorIndexSeed, 1, 100);
91
+ currentActor = address(uint160(uint256(keccak256(abi.encodePacked(actorIndex)))));
92
+ vm.startPrank(currentActor);
93
+ }
94
+
95
+ constructor(IInverseAppProjected1155 _asset, IOrderbookDex _dex) {
96
+ asset = _asset;
97
+ dex = _dex;
98
+ }
99
+
100
+ function createSellOrder(
101
+ uint256 assetId,
102
+ uint256 assetAmount,
103
+ uint256 price,
104
+ uint256 actorIndexSeed
105
+ ) public useActor(actorIndexSeed) returns (uint256) {
106
+ // Bound amount and price to reasonable limits, mint the asset and set approval for the dex
107
+ assetAmount = bound(assetAmount, 1, MAX_ASSET_AMOUNT);
108
+ price = bound(price, 1, MAX_PRICE);
109
+ assetId = asset.mint(assetAmount, "");
110
+ asset.setApprovalForAll(address(dex), true);
111
+
112
+ // Take note of the previous order id
113
+ previousOrderId = dex.currentOrderId(address(asset));
114
+
115
+ uint256 orderCreationFee = dex.orderCreationFee();
116
+ vm.deal(currentActor, orderCreationFee);
117
+
118
+ // Execute the sell order creation
119
+ return
120
+ dex.createSellOrder{value: orderCreationFee}(
121
+ address(asset),
122
+ assetId,
123
+ assetAmount,
124
+ price
125
+ );
126
+ }
127
+
128
+ function createBatchSellOrder(
129
+ uint256[] memory assetAmounts,
130
+ uint256[] memory pricesPerAssets,
131
+ uint256 actorIndexSeed
132
+ ) public useActor(actorIndexSeed) returns (uint256[] memory) {
133
+ // Use the smaller of the input arrays length and bound it to a reasonable limit
134
+ uint256 smallestLength = assetAmounts.length;
135
+ if (pricesPerAssets.length < smallestLength) {
136
+ smallestLength = pricesPerAssets.length;
137
+ }
138
+ smallestLength = bound(smallestLength, 0, MAX_BATCH_CREATE_SELL_ORDER);
139
+
140
+ // Bound amounts and prices to reasonable limits, mint the assets and set approval for the dex
141
+ uint256[] memory newAssetIds = new uint256[](smallestLength);
142
+ uint256[] memory newAssetAmounts = new uint256[](smallestLength);
143
+ uint256[] memory newPricesPerAssets = new uint256[](smallestLength);
144
+ for (uint256 i; i < smallestLength; ++i) {
145
+ newAssetAmounts[i] = bound(assetAmounts[i], 1, MAX_ASSET_AMOUNT);
146
+ newPricesPerAssets[i] = bound(pricesPerAssets[i], 1, MAX_PRICE);
147
+ newAssetIds[i] = asset.mint(newAssetAmounts[i], "");
148
+ }
149
+ asset.setApprovalForAll(address(dex), true);
150
+
151
+ uint256 orderCreationFee = dex.orderCreationFee();
152
+ vm.deal(currentActor, orderCreationFee * newAssetIds.length);
153
+
154
+ // Execute the batch sell order creation
155
+ return
156
+ dex.createBatchSellOrder{value: orderCreationFee * newAssetIds.length}(
157
+ address(asset),
158
+ newAssetIds,
159
+ newAssetAmounts,
160
+ newPricesPerAssets
161
+ );
162
+ }
163
+
164
+ function fillOrdersExactEth(
165
+ uint256 minimumAsset,
166
+ uint256[] memory orderIds,
167
+ uint256 actorIndexSeed
168
+ ) public payable useActor(actorIndexSeed) {
169
+ // Bound the input length to a reasonable limit
170
+ uint256 inputLength = orderIds.length;
171
+ inputLength = bound(inputLength, 0, MAX_ORDER_FILL_LENGTH);
172
+
173
+ // Calculate the sum of the asset amounts and prices of the orders
174
+ uint256 sumAssetAmount;
175
+ uint256 sumPrice;
176
+ for (uint256 i; i < inputLength; ++i) {
177
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderIds[i]);
178
+
179
+ if (!orderIdUsed[orderIds[i]]) {
180
+ sumAssetAmount += order.assetAmount;
181
+ sumPrice += order.pricePerAsset * order.assetAmount;
182
+ orderIdUsed[orderIds[i]] = true;
183
+ newOrderIds.push(orderIds[i]);
184
+ }
185
+ }
186
+ inputLength = newOrderIds.length;
187
+
188
+ // Cap the asset amount to the sum of the asset amounts of the orders
189
+ if (minimumAsset > sumAssetAmount) {
190
+ minimumAsset = sumAssetAmount;
191
+ }
192
+
193
+ // Set current actor's balance to expected total price to avoid revert
194
+ uint256 value = sumPrice;
195
+ (, uint256 takerFee) = dex.getAssetAppliedFees(address(asset));
196
+ value += (value * takerFee) / 10000;
197
+ vm.deal(currentActor, value);
198
+
199
+ // Take note of buyer's tokens balances and orders' asset amounts before filling orders
200
+ // for the assertions later
201
+ uint256[] memory orderAssetAmountBefore = new uint256[](inputLength);
202
+ for (uint256 i; i < inputLength; ++i) {
203
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), newOrderIds[i]);
204
+ buyerTokenBalanceBefore[order.assetId] = asset.balanceOf(currentActor, order.assetId);
205
+ orderAssetAmountBefore[i] = order.assetAmount;
206
+ }
207
+
208
+ // Execute the fills
209
+ dex.fillOrdersExactEth{value: value}(address(asset), minimumAsset, newOrderIds);
210
+
211
+ // Calculate the expected token balance gain for each token
212
+ for (uint256 i; i < inputLength; ++i) {
213
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), newOrderIds[i]);
214
+ expectedTokenBalanceGain[order.assetId] +=
215
+ orderAssetAmountBefore[i] -
216
+ order.assetAmount;
217
+ }
218
+
219
+ // Assert that the buyer's token balances have increased by the expected amount (that was credited from the orders' asset amounts)
220
+ for (uint256 i; i < inputLength; ++i) {
221
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), newOrderIds[i]);
222
+ assertEq(
223
+ asset.balanceOf(currentActor, order.assetId),
224
+ buyerTokenBalanceBefore[order.assetId] + expectedTokenBalanceGain[order.assetId]
225
+ );
226
+ }
227
+
228
+ // Clean-up
229
+ for (uint256 i; i < inputLength; ++i) {
230
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), newOrderIds[i]);
231
+ delete expectedTokenBalanceGain[order.assetId];
232
+ delete orderIdUsed[newOrderIds[i]];
233
+ }
234
+ delete newOrderIds;
235
+ }
236
+
237
+ function fillOrdersExactAsset(
238
+ uint256 assetAmount,
239
+ uint256[] memory orderIds,
240
+ uint256 actorIndexSeed
241
+ ) public payable useActor(actorIndexSeed) {
242
+ // Bound the input length to a reasonable limit
243
+ uint256 inputLength = orderIds.length;
244
+ inputLength = bound(inputLength, 0, MAX_ORDER_FILL_LENGTH);
245
+
246
+ // Calculate the sum of the asset amounts and prices of the orders
247
+ uint256 sumAssetAmount;
248
+ uint256 sumPrice;
249
+ for (uint256 i; i < inputLength; ++i) {
250
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderIds[i]);
251
+
252
+ if (!orderIdUsed[orderIds[i]]) {
253
+ sumAssetAmount += order.assetAmount;
254
+ sumPrice += order.pricePerAsset * order.assetAmount;
255
+ orderIdUsed[orderIds[i]] = true;
256
+ newOrderIds.push(orderIds[i]);
257
+ }
258
+ }
259
+ inputLength = newOrderIds.length;
260
+
261
+ // Cap the asset amount to the sum of the asset amounts of the orders
262
+ if (assetAmount > sumAssetAmount) {
263
+ assetAmount = sumAssetAmount;
264
+ }
265
+
266
+ // Set current actor's balance to expected total price plus 100 to avoid revert and
267
+ // to provide surplus which should be refunded
268
+ uint256 value = sumPrice + 100;
269
+ (, uint256 takerFee) = dex.getAssetAppliedFees(address(asset));
270
+ value += (value * takerFee) / 10000;
271
+ vm.deal(currentActor, value);
272
+
273
+ // Take note of buyer's tokens balances and orders' asset amounts before filling orders
274
+ // for the assertions later
275
+ uint256[] memory orderAssetAmountBefore = new uint256[](inputLength);
276
+ for (uint256 i; i < inputLength; ++i) {
277
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), newOrderIds[i]);
278
+ buyerTokenBalanceBefore[order.assetId] = asset.balanceOf(currentActor, order.assetId);
279
+ orderAssetAmountBefore[i] = order.assetAmount;
280
+ }
281
+
282
+ // Execute the fills
283
+ dex.fillOrdersExactAsset{value: value}(address(asset), assetAmount, newOrderIds);
284
+
285
+ // Calculate the expected token balance gain for each token
286
+ for (uint256 i; i < inputLength; ++i) {
287
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), newOrderIds[i]);
288
+ expectedTokenBalanceGain[order.assetId] +=
289
+ orderAssetAmountBefore[i] -
290
+ order.assetAmount;
291
+ }
292
+
293
+ // Assert that the buyer's token balances have increased by the expected amount (that was credited from the orders' asset amounts)
294
+ for (uint256 i; i < inputLength; ++i) {
295
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), newOrderIds[i]);
296
+ assertEq(
297
+ asset.balanceOf(currentActor, order.assetId),
298
+ buyerTokenBalanceBefore[order.assetId] + expectedTokenBalanceGain[order.assetId]
299
+ );
300
+ }
301
+
302
+ // Clean-up
303
+ for (uint256 i; i < inputLength; ++i) {
304
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), newOrderIds[i]);
305
+ delete expectedTokenBalanceGain[order.assetId];
306
+ delete orderIdUsed[newOrderIds[i]];
307
+ }
308
+ delete newOrderIds;
309
+ }
310
+
311
+ function cancelSellOrder(
312
+ uint256 orderId,
313
+ uint256 actorIndexSeed
314
+ ) public useActor(actorIndexSeed) {
315
+ // If the order does not exist, get the last order id
316
+ if (dex.getOrder(address(asset), orderId).assetAmount == 0) {
317
+ orderId = dex.currentOrderId(address(asset));
318
+ // If there are no orders, return
319
+ if (orderId == 0) {
320
+ return;
321
+ } else {
322
+ orderId--;
323
+ }
324
+ }
325
+
326
+ // Assert that you can't cancel an order if you're not the seller
327
+ address seller = dex.getOrder(address(asset), orderId).seller;
328
+ if (currentActor != seller) {
329
+ vm.expectRevert(
330
+ abi.encodeWithSelector(OrderbookDex.Unauthorized.selector, currentActor)
331
+ );
332
+ dex.cancelSellOrder(address(asset), orderId);
333
+ }
334
+
335
+ // Prank the order's seller
336
+ vm.startPrank(seller);
337
+
338
+ // Execute the cancel
339
+ dex.cancelSellOrder(address(asset), orderId);
340
+ }
341
+
342
+ function cancelBatchSellOrder(
343
+ uint256[] memory orderIds,
344
+ uint256 actorIndexSeed
345
+ ) public useActor(actorIndexSeed) {
346
+ // Bound the input length to a reasonable limit
347
+ uint256 len = bound(orderIds.length, 0, MAX_BATCH_CANCEL_SELL_ORDER);
348
+
349
+ // Create new orders if the order does not exist or the seller is not the current actor
350
+ uint256[] memory _newOrderIds = new uint256[](len);
351
+ for (uint256 i; i < len; ++i) {
352
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), orderIds[i]);
353
+ if (order.assetAmount == 0 || order.seller != currentActor) {
354
+ uint256 x = uint256(keccak256(abi.encodePacked(i)));
355
+ _newOrderIds[i] = this.createSellOrder(x, x, x, actorIndexSeed);
356
+ } else {
357
+ _newOrderIds[i] = orderIds[i];
358
+ }
359
+ }
360
+
361
+ // If this.createSellOrder was called, the prank was stopped, so we need to start it again
362
+ if (len > 0) {
363
+ vm.startPrank(currentActor);
364
+ }
365
+
366
+ // Execute the batch cancel
367
+ dex.cancelBatchSellOrder(address(asset), _newOrderIds);
368
+ }
369
+
370
+ fallback() external payable {}
371
+ }
372
+
373
+ contract OrderbookDexInvariantTest is CTest, ERC1155Holder {
374
+ using Address for address payable;
375
+
376
+ CheatCodes vm = CheatCodes(HEVM_ADDRESS);
377
+ OrderbookDex public dex;
378
+ OrderbookDexHandler public dexHandler;
379
+ IInverseAppProjected1155 asset;
380
+ AssetHandler public assetHandler;
381
+ uint256 makerFee = 40;
382
+ uint256 takerFee = 60;
383
+ uint256 orderCreationFee = 10000 gwei;
384
+
385
+ function setUp() public {
386
+ asset = new InverseAppProjected1155("Gold", "GOLD", address(this));
387
+ dex = OrderbookDex(
388
+ address(
389
+ new OrderbookDexProxy(
390
+ address(new OrderbookDex()),
391
+ address(this),
392
+ makerFee,
393
+ takerFee,
394
+ orderCreationFee
395
+ )
396
+ )
397
+ );
398
+ dexHandler = new OrderbookDexHandler(asset, dex);
399
+ assetHandler = new AssetHandler(asset, dex);
400
+ targetContract(address(assetHandler));
401
+ targetContract(address(dexHandler));
402
+ }
403
+
404
+ function invariant_ordersAssetAmountEqualsContractTokenBalance() public {
405
+ for (uint256 i; i < dex.currentOrderId(address(asset)); ++i) {
406
+ IOrderbookDex.Order memory order = dex.getOrder(address(asset), i);
407
+ assertEq(order.assetAmount, asset.balanceOf(address(dex), order.assetId));
408
+ }
409
+ }
410
+
411
+ function invariant_orderIdIsIncremental() public {
412
+ uint256 currentId = dex.currentOrderId(address(asset));
413
+ uint256 previousId = dexHandler.previousOrderId();
414
+ if (currentId == previousId) {
415
+ assertEq(currentId, 0);
416
+ return;
417
+ }
418
+ assertGe(currentId, previousId);
419
+ }
420
+
421
+ function invariant_contractHasBalanceAtLeastOfCollectedFees() public {
422
+ assertGe(address(dex).balance, dex.collectedFees());
423
+ }
424
+
425
+ receive() external payable {}
426
+ }
@@ -0,0 +1,115 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import "../lib/cheatcodes.sol";
5
+ import "../lib/console.sol";
6
+ import "../lib/ctest.sol";
7
+
8
+ import "../../src/contracts/PaimaL2Contract.sol";
9
+
10
+ contract PaimaL2ContractTest is CTest {
11
+ event PaimaGameInteraction(address indexed userAddress, bytes data, uint256 value);
12
+
13
+ CheatCodes cheats = CheatCodes(HEVM_ADDRESS);
14
+ PaimaL2Contract strg;
15
+
16
+ address account1 = 0x766FCe3d50d795Fe6DcB1020AB58bccddd5C5c77;
17
+ address account2 = 0x078D888E40faAe0f32594342c85940AF3949E666;
18
+
19
+ uint256 constant MAX_INT = 2 ** 256 - 1;
20
+ uint256 constant FEE = 1e16;
21
+
22
+ function setUp() public {
23
+ strg = new PaimaL2Contract(address(account1), FEE);
24
+ cheats.deal(account1, 100 ether);
25
+ cheats.deal(account2, 100 ether);
26
+ }
27
+
28
+ function testWithdraw() public {
29
+ cheats.prank(account1);
30
+ strg.withdrawFunds();
31
+ assertEq(address(strg).balance, 0);
32
+ }
33
+
34
+ function testCannotWithdraw() public {
35
+ cheats.prank(account2);
36
+ cheats.expectRevert("Only owner can withdraw funds");
37
+ strg.withdrawFunds();
38
+ }
39
+
40
+ function testCannotStoreWithoutFee() public {
41
+ cheats.startPrank(account2);
42
+ cheats.expectRevert("Sufficient funds required to submit game input");
43
+ strg.paimaSubmitGameInput("0x123456");
44
+ cheats.expectRevert("Sufficient funds required to submit game input");
45
+ strg.paimaSubmitGameInput{value: FEE - 1}("0x123456");
46
+ cheats.stopPrank();
47
+ }
48
+
49
+ function testStore() public {
50
+ cheats.prank(account2);
51
+ bytes memory data = "0x123456";
52
+ cheats.expectEmit(true, true, true, true);
53
+ emit PaimaGameInteraction(account2, data, FEE);
54
+ strg.paimaSubmitGameInput{value: FEE}(data);
55
+ assertEq(address(strg).balance, FEE);
56
+ }
57
+
58
+ function testStoreAndWithdraw() public {
59
+ cheats.startPrank(account2);
60
+ bytes memory data = "0x123456";
61
+ strg.paimaSubmitGameInput{value: FEE}(data);
62
+ strg.paimaSubmitGameInput{value: FEE}(data);
63
+ strg.paimaSubmitGameInput{value: FEE}(data);
64
+ cheats.stopPrank();
65
+ assertEq(address(strg).balance, FEE * 3);
66
+ cheats.prank(account1);
67
+ strg.withdrawFunds();
68
+ assertEq(address(strg).balance, 0);
69
+ }
70
+
71
+ function testChangeOwner() public {
72
+ assertEq(strg.owner(), account1);
73
+ cheats.prank(account1);
74
+ strg.setOwner(account2);
75
+ assertEq(strg.owner(), account2);
76
+ cheats.prank(account2);
77
+ strg.withdrawFunds();
78
+ }
79
+
80
+ function testCannotChangeOwner() public {
81
+ assertEq(strg.owner(), account1);
82
+ cheats.prank(account2);
83
+ cheats.expectRevert("Only owner can change owner");
84
+ strg.setOwner(account1);
85
+ assertEq(strg.owner(), account1);
86
+ }
87
+
88
+ function testChangeFee() public {
89
+ uint256 newFee = FEE * 2;
90
+ assertEq(strg.owner(), account1);
91
+ assertEq(strg.fee(), FEE);
92
+ cheats.prank(account1);
93
+ strg.setFee(newFee);
94
+ assertEq(strg.fee(), newFee);
95
+
96
+ bytes memory data = "0x123456";
97
+ cheats.prank(account2);
98
+ cheats.expectRevert("Sufficient funds required to submit game input");
99
+ strg.paimaSubmitGameInput{value: newFee - 1}(data);
100
+
101
+ cheats.prank(account2);
102
+ cheats.expectEmit(true, true, true, true);
103
+ emit PaimaGameInteraction(account2, data, newFee);
104
+ strg.paimaSubmitGameInput{value: newFee}(data);
105
+ assertEq(address(strg).balance, newFee);
106
+ }
107
+
108
+ function testCannotChangeFee() public {
109
+ assertEq(strg.fee(), FEE);
110
+ cheats.prank(account2);
111
+ cheats.expectRevert("Only owner can change fee");
112
+ strg.setFee(FEE * 2);
113
+ assertEq(strg.fee(), FEE);
114
+ }
115
+ }