From 8411ed87c7907e12fe97b04da85d740b9cc5b3cf Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Fri, 20 Mar 2026 10:14:14 +0000 Subject: [PATCH] [#44] Reorder createStoryline to CEI pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move state writes (storylineCount++, storylines[id] = ...) before external calls (BOND.createToken, BOND.updateBondCreator). Token address stored as address(0) placeholder initially, updated after the interaction completes. If createToken reverts, the entire tx reverts so the placeholder is harmless. No behavioral change — all 45 existing tests pass unchanged. Fixes #44 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/StoryFactory.sol | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/StoryFactory.sol b/src/StoryFactory.sol index 87dbd06..f1bde35 100644 --- a/src/StoryFactory.sol +++ b/src/StoryFactory.sol @@ -119,14 +119,22 @@ contract StoryFactory { payable returns (uint256 storylineId) { + // CHECKS require(bytes(title).length > 0, "Empty title"); require(bytes(openingCID).length >= 46 && bytes(openingCID).length <= 100, "Invalid CID"); require(openingHash != bytes32(0), "Empty hash"); + // EFFECTS (state changes before external calls) storylineId = ++storylineCount; + storylines[storylineId] = Storyline({ + writer: msg.sender, + token: address(0), // placeholder, updated after interaction + plotCount: 1, + lastPlotTime: uint40(block.timestamp), + hasDeadline: hasDeadline + }); - // 1. Create token on Mint Club V2 bonding curve - // Factory becomes initial creator; we transfer to writer below + // INTERACTIONS (external calls last) TokenParams memory tp = TokenParams({name: title, symbol: string(abi.encodePacked("PL-", _uint2str(storylineId)))}); @@ -141,23 +149,15 @@ contract StoryFactory { address tokenAddress = BOND.createToken{value: msg.value}(tp, bp); - // 2. Transfer creator role to writer (royalties go directly to them) // Trust assumption: MCV2_Bond is Mint Club's audited contract. // updateBondCreator is expected to succeed if createToken succeeded. // No return value to check — if this silently fails, royalties // would accrue to the factory with no recovery path. BOND.updateBondCreator(tokenAddress, msg.sender); - // 3. Store storyline - storylines[storylineId] = Storyline({ - writer: msg.sender, - token: tokenAddress, - plotCount: 1, - lastPlotTime: uint40(block.timestamp), - hasDeadline: hasDeadline - }); + // Update token address after interaction + storylines[storylineId].token = tokenAddress; - // 4. Emit events emit StorylineCreated(storylineId, msg.sender, tokenAddress, title, hasDeadline, openingCID, openingHash); emit PlotChained(storylineId, 0, msg.sender, title, openingCID, openingHash); }