Skip to content

Commit 7d61c9f

Browse files
committed
Fix and add tests
1 parent 7fe0239 commit 7d61c9f

4 files changed

Lines changed: 629 additions & 1 deletion

File tree

contracts/src/Ethscriptions.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,9 @@ contract Ethscriptions is ERC721EthscriptionsSequentialEnumerableUpgradeable {
582582
}
583583

584584
page.items = items;
585-
page.limit = resultsCount;
585+
// `limit` reflects the effective (clamped) page size requested,
586+
// while the actual number of returned items is `items.length`.
587+
page.limit = effectiveLimit;
586588
page.nextStart = start + resultsCount;
587589
page.hasMore = page.nextStart < totalCount;
588590
}

contracts/test/PaginationGas.t.sol

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.24;
3+
4+
import "./TestSetup.sol";
5+
import "forge-std/console.sol";
6+
7+
contract PaginationGasTest is TestSetup {
8+
uint256 constant SMALL_CONTENT_SIZE = 10; // 10 bytes
9+
uint256 constant MEDIUM_CONTENT_SIZE = 100; // 100 bytes
10+
uint256 constant LARGE_CONTENT_SIZE = 1000; // 1KB
11+
uint256 constant HUGE_CONTENT_SIZE = 10000; // 10KB
12+
13+
// Create ethscriptions with different content sizes for testing
14+
function setUp() public override {
15+
super.setUp();
16+
17+
// Create ethscriptions with various content sizes
18+
// We'll create 100 ethscriptions to test pagination properly
19+
for (uint256 i = 0; i < 100; i++) {
20+
bytes32 txHash = bytes32(uint256(0x1000000 + i));
21+
address creator = address(uint160(0x100 + (i % 10))); // 10 different creators
22+
address owner = address(uint160(0x200 + (i % 5))); // 5 different owners
23+
24+
// Vary content size based on index
25+
bytes memory content;
26+
if (i % 4 == 0) {
27+
content = new bytes(SMALL_CONTENT_SIZE);
28+
} else if (i % 4 == 1) {
29+
content = new bytes(MEDIUM_CONTENT_SIZE);
30+
} else if (i % 4 == 2) {
31+
content = new bytes(LARGE_CONTENT_SIZE);
32+
} else {
33+
content = new bytes(HUGE_CONTENT_SIZE);
34+
}
35+
36+
// Fill content with some data
37+
for (uint256 j = 0; j < content.length; j++) {
38+
content[j] = bytes1(uint8(j % 256));
39+
}
40+
41+
vm.prank(creator);
42+
Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({
43+
ethscriptionId: txHash,
44+
contentUriHash: keccak256(abi.encodePacked("uri", i)),
45+
initialOwner: owner,
46+
content: content,
47+
mimetype: "application/octet-stream",
48+
esip6: false,
49+
protocolParams: Ethscriptions.ProtocolParams({
50+
protocolName: "",
51+
operation: "",
52+
data: ""
53+
})
54+
});
55+
56+
ethscriptions.createEthscription(params);
57+
}
58+
59+
console.log("Setup complete: Created 100 ethscriptions");
60+
console.log("- 25 with small content (10 bytes)");
61+
console.log("- 25 with medium content (100 bytes)");
62+
console.log("- 25 with large content (1KB)");
63+
console.log("- 25 with huge content (10KB)");
64+
console.log("");
65+
}
66+
67+
function testGas_GetEthscriptions_WithContent() public view {
68+
console.log("=== Testing getEthscriptions WITH content ===");
69+
console.log("");
70+
71+
// Test various page sizes with content
72+
uint256[] memory pageSizes = new uint256[](7);
73+
pageSizes[0] = 1;
74+
pageSizes[1] = 10;
75+
pageSizes[2] = 20;
76+
pageSizes[3] = 30;
77+
pageSizes[4] = 40;
78+
pageSizes[5] = 50;
79+
pageSizes[6] = 60; // Should be clamped to 50
80+
81+
for (uint256 i = 0; i < pageSizes.length; i++) {
82+
uint256 gasStart = gasleft();
83+
Ethscriptions.PaginatedEthscriptionsResponse memory result = ethscriptions.getEthscriptions(0, pageSizes[i], true);
84+
uint256 gasUsed = gasStart - gasleft();
85+
86+
console.log("Requested:", pageSizes[i], "items");
87+
console.log(" Returned:", result.items.length, "items");
88+
console.log(" Gas used:", gasUsed);
89+
console.log(" Gas per item:", result.items.length > 0 ? gasUsed / result.items.length : 0);
90+
console.log("");
91+
}
92+
}
93+
94+
function testGas_GetEthscriptions_WithoutContent() public view {
95+
console.log("=== Testing getEthscriptions WITHOUT content ===");
96+
console.log("");
97+
98+
// Test various page sizes without content
99+
uint256[] memory pageSizes = new uint256[](10);
100+
pageSizes[0] = 1;
101+
pageSizes[1] = 10;
102+
pageSizes[2] = 50;
103+
pageSizes[3] = 100;
104+
pageSizes[4] = 200;
105+
pageSizes[5] = 300;
106+
pageSizes[6] = 500;
107+
pageSizes[7] = 750;
108+
pageSizes[8] = 1000;
109+
pageSizes[9] = 1500; // Should be clamped to 1000
110+
111+
for (uint256 i = 0; i < pageSizes.length; i++) {
112+
uint256 gasStart = gasleft();
113+
Ethscriptions.PaginatedEthscriptionsResponse memory result = ethscriptions.getEthscriptions(0, pageSizes[i], false);
114+
uint256 gasUsed = gasStart - gasleft();
115+
116+
console.log("Requested:", pageSizes[i], "items");
117+
console.log(" Returned:", result.items.length, "items");
118+
console.log(" Gas used:", gasUsed);
119+
console.log(" Gas per item:", result.items.length > 0 ? gasUsed / result.items.length : 0);
120+
console.log("");
121+
}
122+
}
123+
124+
function testGas_GetOwnerEthscriptions_WithContent() public view {
125+
console.log("=== Testing getOwnerEthscriptions WITH content ===");
126+
console.log("");
127+
128+
// Test with owner that has 20 ethscriptions (address(0x200))
129+
address targetOwner = address(0x200);
130+
uint256 ownerBalance = ethscriptions.balanceOf(targetOwner);
131+
console.log("Owner balance:", ownerBalance);
132+
console.log("");
133+
134+
// Test various page sizes
135+
uint256[] memory pageSizes = new uint256[](5);
136+
pageSizes[0] = 5;
137+
pageSizes[1] = 10;
138+
pageSizes[2] = 15;
139+
pageSizes[3] = 20;
140+
pageSizes[4] = 30; // More than owner has
141+
142+
for (uint256 i = 0; i < pageSizes.length; i++) {
143+
uint256 gasStart = gasleft();
144+
Ethscriptions.PaginatedEthscriptionsResponse memory result = ethscriptions.getOwnerEthscriptions(targetOwner, 0, pageSizes[i], true);
145+
uint256 gasUsed = gasStart - gasleft();
146+
147+
console.log("Requested:", pageSizes[i], "items");
148+
console.log(" Returned:", result.items.length, "items");
149+
console.log(" Gas used:", gasUsed);
150+
console.log(" Gas per item:", result.items.length > 0 ? gasUsed / result.items.length : 0);
151+
console.log("");
152+
}
153+
}
154+
155+
function testGas_GetOwnerEthscriptions_WithoutContent() public view {
156+
console.log("=== Testing getOwnerEthscriptions WITHOUT content ===");
157+
console.log("");
158+
159+
// Test with owner that has 20 ethscriptions
160+
address targetOwner = address(0x200);
161+
uint256 ownerBalance = ethscriptions.balanceOf(targetOwner);
162+
console.log("Owner balance:", ownerBalance);
163+
console.log("");
164+
165+
// Test various page sizes
166+
uint256[] memory pageSizes = new uint256[](5);
167+
pageSizes[0] = 5;
168+
pageSizes[1] = 10;
169+
pageSizes[2] = 15;
170+
pageSizes[3] = 20;
171+
pageSizes[4] = 30; // More than owner has
172+
173+
for (uint256 i = 0; i < pageSizes.length; i++) {
174+
uint256 gasStart = gasleft();
175+
Ethscriptions.PaginatedEthscriptionsResponse memory result = ethscriptions.getOwnerEthscriptions(targetOwner, 0, pageSizes[i], false);
176+
uint256 gasUsed = gasStart - gasleft();
177+
178+
console.log("Requested:", pageSizes[i], "items");
179+
console.log(" Returned:", result.items.length, "items");
180+
console.log(" Gas used:", gasUsed);
181+
console.log(" Gas per item:", result.items.length > 0 ? gasUsed / result.items.length : 0);
182+
console.log("");
183+
}
184+
}
185+
186+
function testGas_EdgeCases() public view {
187+
console.log("=== Testing Edge Cases ===");
188+
console.log("");
189+
190+
// Test with start beyond total supply
191+
uint256 gasStart = gasleft();
192+
Ethscriptions.PaginatedEthscriptionsResponse memory result1 = ethscriptions.getEthscriptions(200, 10, true);
193+
uint256 gasUsed = gasStart - gasleft();
194+
console.log("Start beyond total (200, 10):");
195+
console.log(" Returned:", result1.items.length, "items");
196+
console.log(" Gas used:", gasUsed);
197+
console.log("");
198+
199+
// Test with limit = 0 (should revert)
200+
console.log("Limit = 0:");
201+
try ethscriptions.getEthscriptions(0, 0, true) returns (Ethscriptions.PaginatedEthscriptionsResponse memory) {
202+
console.log(" ERROR: Should have reverted!");
203+
} catch {
204+
console.log(" Correctly reverted with InvalidPaginationLimit");
205+
}
206+
console.log("");
207+
208+
// Test pagination continuation
209+
gasStart = gasleft();
210+
Ethscriptions.PaginatedEthscriptionsResponse memory page1 = ethscriptions.getEthscriptions(0, 30, true);
211+
gasUsed = gasStart - gasleft();
212+
console.log("First page (0, 30):");
213+
console.log(" Returned:", page1.items.length, "items");
214+
console.log(" Has more:", page1.hasMore);
215+
console.log(" Next start:", page1.nextStart);
216+
console.log(" Gas used:", gasUsed);
217+
console.log("");
218+
219+
if (page1.hasMore) {
220+
gasStart = gasleft();
221+
Ethscriptions.PaginatedEthscriptionsResponse memory page2 = ethscriptions.getEthscriptions(page1.nextStart, 30, true);
222+
gasUsed = gasStart - gasleft();
223+
console.log("Second page starting at:", page1.nextStart);
224+
console.log(" Returned:", page2.items.length, "items");
225+
console.log(" Has more:", page2.hasMore);
226+
console.log(" Gas used:", gasUsed);
227+
console.log("");
228+
}
229+
}
230+
231+
function testGas_MaximumLimits() public {
232+
console.log("=== Testing Maximum Safe Limits ===");
233+
console.log("");
234+
235+
// Test approaching gas limits with content
236+
console.log("Testing max with content (trying different sizes):");
237+
uint256[] memory testSizes = new uint256[](5);
238+
testSizes[0] = 40;
239+
testSizes[1] = 45;
240+
testSizes[2] = 50;
241+
testSizes[3] = 55;
242+
testSizes[4] = 60;
243+
244+
for (uint256 i = 0; i < testSizes.length; i++) {
245+
try ethscriptions.getEthscriptions(0, testSizes[i], true) returns (Ethscriptions.PaginatedEthscriptionsResponse memory result) {
246+
uint256 gasStart = gasleft();
247+
ethscriptions.getEthscriptions(0, testSizes[i], true);
248+
uint256 gasUsed = gasStart - gasleft();
249+
console.log(" Size:", testSizes[i]);
250+
console.log(" Returned items:", result.items.length);
251+
console.log(" Gas used:", gasUsed);
252+
} catch {
253+
console.log(" Size FAILED:", testSizes[i]);
254+
}
255+
}
256+
console.log("");
257+
258+
// Test approaching gas limits without content
259+
console.log("Testing max without content (trying different sizes):");
260+
uint256[] memory testSizesNoContent = new uint256[](5);
261+
testSizesNoContent[0] = 800;
262+
testSizesNoContent[1] = 900;
263+
testSizesNoContent[2] = 1000;
264+
testSizesNoContent[3] = 1100;
265+
testSizesNoContent[4] = 1200;
266+
267+
// Need to create more ethscriptions for this test
268+
if (ethscriptions.totalSupply() < 1200) {
269+
console.log(" (Skipping - need more ethscriptions for full test)");
270+
} else {
271+
for (uint256 i = 0; i < testSizesNoContent.length; i++) {
272+
try ethscriptions.getEthscriptions(0, testSizesNoContent[i], false) returns (Ethscriptions.PaginatedEthscriptionsResponse memory result) {
273+
uint256 gasStart = gasleft();
274+
ethscriptions.getEthscriptions(0, testSizesNoContent[i], false);
275+
uint256 gasUsed = gasStart - gasleft();
276+
console.log(" Size:", testSizesNoContent[i]);
277+
console.log(" Returned items:", result.items.length);
278+
console.log(" Gas used:", gasUsed);
279+
} catch {
280+
console.log(" Size FAILED:", testSizesNoContent[i]);
281+
}
282+
}
283+
}
284+
}
285+
}

0 commit comments

Comments
 (0)