Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions script/E2ETest.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ contract E2ETest is Script {
// -----------------------------------------------------------------------
// Base mainnet addresses
// -----------------------------------------------------------------------
StoryFactory constant FACTORY = StoryFactory(0xc278F4099298118efA8dF30DF0F4876632571948);
StoryFactory constant FACTORY = StoryFactory(0x27B4FCf333f29a3865b3B76ea00C955D7b64BD0F);
IERC20 constant PL_TEST = IERC20(0xF8A2C39111FCEB9C950aAf28A9E34EBaD99b85C1);
IMCV2_Bond constant BOND = IMCV2_Bond(0xc5a076cad94176c2996B32d8466Be1cE757FAa27);

Expand Down Expand Up @@ -434,11 +434,30 @@ contract E2ETest is Script {
console.log("[F5] Buy/sell royalty diff PASS cost=%d refund=%d", buyCost, sellRefund);
scenariosPassed++;

// F6: updateCurve by owner + create storyline with new curve
uint128[] memory newRanges = new uint128[](2);
newRanges[0] = 500_000e18;
newRanges[1] = 1_000_000e18;
uint128[] memory newPrices = new uint128[](2);
newPrices[0] = 2e15; // 0.002 PL_TEST (double the original)
newPrices[1] = 2e18;
FACTORY.updateCurve(newRanges, newPrices);
console.log("[F6] updateCurve by owner PASS");

uint256 idF6 = FACTORY.createStoryline{value: creationFee}("Updated Curve Story", CID_46, HASH_A, false);
require(idF6 > 0, "F6: failed to create with new curve");
console.log("[F6] Create after updateCurve PASS storylineId=%d", idF6);
scenariosPassed += 2;

// Restore original curve (500 steps) to leave contract in clean state
// Not practical in broadcast — skip restore. The curve is now 2-step.

// Serialize edge case storyline IDs
string memory fKey = "edgeCasesF";
vm.serializeUint(fKey, "f1StorylineId", idF1);
vm.serializeAddress(fKey, "f1Token", tokenF1);
string memory fJson = vm.serializeUint(fKey, "f2StorylineId", idF2);
vm.serializeUint(fKey, "f2StorylineId", idF2);
string memory fJson = vm.serializeUint(fKey, "f6StorylineId", idF6);
vm.serializeString(resultsJson, "edgeCasesF", fJson);
}
}
111 changes: 110 additions & 1 deletion script/E2ETestReverts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {IERC20} from "../src/interfaces/IERC20.sol";
/// under --broadcast. Run with `forge script` (no --broadcast flag).
/// Requires the main E2ETest to have run first (needs existing storylines).
contract E2ETestReverts is Script {
StoryFactory constant FACTORY = StoryFactory(0xc278F4099298118efA8dF30DF0F4876632571948);
StoryFactory constant FACTORY = StoryFactory(0x27B4FCf333f29a3865b3B76ea00C955D7b64BD0F);
IERC20 constant PL_TEST = IERC20(0xF8A2C39111FCEB9C950aAf28A9E34EBaD99b85C1);
IMCV2_Bond constant BOND = IMCV2_Bond(0xc5a076cad94176c2996B32d8466Be1cE757FAa27);

Expand Down Expand Up @@ -145,6 +145,44 @@ contract E2ETestReverts is Script {
scenariosPassed++;
}

// E10: chainPlot with empty title (new validation from #42)
vm.prank(deployer);
try FACTORY.chainPlot(idA1, "", CID_46, HASH_A) {
revert("E10: should have reverted");
} catch Error(string memory reason) {
require(keccak256(bytes(reason)) == keccak256("Empty title"), "E10: wrong revert reason");
console.log('[E10] Empty title in chainPlot reverts PASS "Empty title"');
scenariosPassed++;
}

// E11: createStoryline with zero hash
try FACTORY.createStoryline("Test", CID_46, bytes32(0), false) {
revert("E11: should have reverted");
} catch Error(string memory reason) {
require(keccak256(bytes(reason)) == keccak256("Empty hash"), "E11: wrong revert reason");
console.log('[E11] Zero hash in create reverts PASS "Empty hash"');
scenariosPassed++;
}

// E12: chainPlot with zero hash
vm.prank(deployer);
try FACTORY.chainPlot(idA1, "Test", CID_46, bytes32(0)) {
revert("E12: should have reverted");
} catch Error(string memory reason) {
require(keccak256(bytes(reason)) == keccak256("Empty hash"), "E12: wrong revert reason");
console.log('[E12] Zero hash in chainPlot reverts PASS "Empty hash"');
scenariosPassed++;
}

// E13: updateCurve by non-owner
try FACTORY.updateCurve(new uint128[](1), new uint128[](1)) {
revert("E13: should have reverted");
} catch Error(string memory reason) {
require(keccak256(bytes(reason)) == keccak256("Not owner"), "E13: wrong revert reason");
console.log('[E13] Non-owner updateCurve reverts PASS "Not owner"');
scenariosPassed++;
}

// ===== F3: Zero creation fee =====
console.log("");
console.log("--- Group F: Edge Cases (reverts) ---");
Expand All @@ -156,6 +194,77 @@ contract E2ETestReverts is Script {
scenariosPassed++;
}

// ===== Group G: hasSunset view =====
console.log("");
console.log("--- Group G: hasSunset ---");

// G1: hasSunset on active storyline (no deadline) — should be false
bool sunset1 = FACTORY.hasSunset(idA1);
require(!sunset1, "G1: hasSunset should be false for no-deadline storyline");
console.log("[G1] hasSunset (no deadline) = false PASS");
scenariosPassed++;

// G2: hasSunset on expired deadline storyline
// Read storylineA2 which has hasDeadline from e2e-results.json
uint256 idA2 = vm.parseJsonUint(json, ".storylineA2.storylineId");
bool a2HasDeadline = vm.parseJsonBool(json, ".storylineA2.hasDeadline");
if (!a2HasDeadline) {
// A2 has no deadline — hasSunset should be false even after warp
vm.warp(block.timestamp + 365 days);
bool sunset2 = FACTORY.hasSunset(idA2);
require(!sunset2, "G2: hasSunset should be false without deadline even after warp");
console.log("[G2] hasSunset (no deadline, warped) PASS");
} else {
// A2 has deadline — warp past it
vm.warp(block.timestamp + 169 hours);
bool sunset2 = FACTORY.hasSunset(idA2);
require(sunset2, "G2: hasSunset should be true after deadline");
console.log("[G2] hasSunset (expired deadline) PASS");
}
scenariosPassed++;

// ===== Group H: Constructor validations (simulation only) =====
console.log("");
console.log("--- Group H: Constructor validations ---");

uint128[] memory r1 = new uint128[](1);
r1[0] = 1e18;
uint128[] memory p1 = new uint128[](1);
p1[0] = 1e15;

// H1: Zero bond address
try new StoryFactory(address(0), address(1), 1e18, r1, p1) {
revert("H1: should have reverted");
} catch Error(string memory reason) {
require(keccak256(bytes(reason)) == keccak256("Zero bond address"), "H1: wrong revert reason");
console.log('[H1] Zero bond address reverts PASS "Zero bond address"');
scenariosPassed++;
}

// H2: Zero token address
try new StoryFactory(address(1), address(0), 1e18, r1, p1) {
revert("H2: should have reverted");
} catch Error(string memory reason) {
require(keccak256(bytes(reason)) == keccak256("Zero token address"), "H2: wrong revert reason");
console.log('[H2] Zero token address reverts PASS "Zero token address"');
scenariosPassed++;
}

// H3: Too many steps (>1000)
uint128[] memory bigR = new uint128[](1001);
uint128[] memory bigP = new uint128[](1001);
for (uint256 i = 0; i < 1001; i++) {
bigR[i] = uint128(i + 1);
bigP[i] = uint128(i + 1);
}
try new StoryFactory(address(1), address(1), 1e18, bigR, bigP) {
revert("H3: should have reverted");
} catch Error(string memory reason) {
require(keccak256(bytes(reason)) == keccak256("Too many steps"), "H3: wrong revert reason");
console.log('[H3] >1000 steps reverts PASS "Too many steps"');
scenariosPassed++;
}

console.log("");
console.log("=== ALL REVERT TESTS PASSED ===");
console.log("Scenarios passed:", scenariosPassed);
Expand Down