Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 35 additions & 1 deletion src/StoryFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ contract StoryFactory {
);

event Donation(uint256 indexed storylineId, address indexed donor, uint256 amount);
event CurveUpdated(uint256 newStepCount);

// -----------------------------------------------------------------------
// Constants
Expand All @@ -59,14 +60,25 @@ contract StoryFactory {
IMCV2_Bond public immutable BOND;
IERC20 public immutable PLOT_TOKEN;

/// @notice Bonding curve step arrays (same for every storyline, set at deploy)
/// @notice Bonding curve step arrays (used for future storylines, updatable by owner)
uint128[] public stepRanges;
uint128[] public stepPrices;
uint128 public immutable MAX_SUPPLY;

address public owner;

mapping(uint256 => Storyline) public storylines;
uint256 public storylineCount;

// -----------------------------------------------------------------------
// Modifiers
// -----------------------------------------------------------------------

modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}

// -----------------------------------------------------------------------
// Constructor
// -----------------------------------------------------------------------
Expand All @@ -87,6 +99,7 @@ contract StoryFactory {
BOND = IMCV2_Bond(_bond);
PLOT_TOKEN = IERC20(_plotToken);
MAX_SUPPLY = _maxSupply;
owner = msg.sender;
stepRanges = _stepRanges;
stepPrices = _stepPrices;
}
Expand Down Expand Up @@ -129,6 +142,10 @@ 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
Expand Down Expand Up @@ -185,6 +202,23 @@ contract StoryFactory {
return s.hasDeadline && block.timestamp > uint256(s.lastPlotTime) + 168 hours;
}

// -----------------------------------------------------------------------
// updateCurve
// -----------------------------------------------------------------------

/// @notice Update bonding curve parameters for future storylines
/// @dev Existing storylines are unaffected — they already have their token on MCV2
/// @param newRanges New step ranges array
/// @param newPrices New step prices array
function updateCurve(uint128[] calldata newRanges, uint128[] calldata newPrices) external onlyOwner {
require(newRanges.length == newPrices.length, "Step arrays length mismatch");
require(newRanges.length > 0, "Empty step arrays");
require(newRanges.length <= 1000, "Too many steps");
stepRanges = newRanges;
stepPrices = newPrices;
emit CurveUpdated(newRanges.length);
}

// -----------------------------------------------------------------------
// donate
// -----------------------------------------------------------------------
Expand Down
91 changes: 91 additions & 0 deletions test/StoryFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -432,4 +432,95 @@ contract StoryFactoryTest is Test {
vm.expectRevert("Too many steps");
new StoryFactory(address(bond), address(plot), 1e18, ranges, prices);
}

// ===================================================================
// Owner + updateCurve (#43)
// ===================================================================

function test_owner_setInConstructor() public view {
assertEq(factory.owner(), address(this));
}

function test_updateCurve_happy() public {
uint128[] memory newRanges = new uint128[](3);
newRanges[0] = 100e18;
newRanges[1] = 200e18;
newRanges[2] = 300e18;
uint128[] memory newPrices = new uint128[](3);
newPrices[0] = 2e15;
newPrices[1] = 3e15;
newPrices[2] = 4e15;

vm.expectEmit(false, false, false, true);
emit StoryFactory.CurveUpdated(3);

factory.updateCurve(newRanges, newPrices);

assertEq(factory.stepRanges(0), 100e18);
assertEq(factory.stepRanges(1), 200e18);
assertEq(factory.stepRanges(2), 300e18);
assertEq(factory.stepPrices(0), 2e15);
assertEq(factory.stepPrices(1), 3e15);
assertEq(factory.stepPrices(2), 4e15);
}

function test_updateCurve_revert_notOwner() public {
uint128[] memory r = new uint128[](1);
r[0] = 1e18;
uint128[] memory p = new uint128[](1);
p[0] = 1e15;

vm.prank(other);
vm.expectRevert("Not owner");
factory.updateCurve(r, p);
}

function test_updateCurve_revert_mismatchedArrays() public {
uint128[] memory r = new uint128[](2);
uint128[] memory p = new uint128[](1);
r[0] = 1e18;
r[1] = 2e18;
p[0] = 1e15;

vm.expectRevert("Step arrays length mismatch");
factory.updateCurve(r, p);
}

function test_updateCurve_revert_emptyArrays() public {
uint128[] memory r = new uint128[](0);
uint128[] memory p = new uint128[](0);

vm.expectRevert("Empty step arrays");
factory.updateCurve(r, p);
}

function test_updateCurve_revert_tooManySteps() public {
uint128[] memory r = new uint128[](1001);
uint128[] memory p = new uint128[](1001);
for (uint256 i = 0; i < 1001; i++) {
r[i] = uint128(i + 1);
p[i] = uint128(i + 1);
}

vm.expectRevert("Too many steps");
factory.updateCurve(r, p);
}

function test_updateCurve_newStorylineUsesNewParams() public {
// Update curve to 1 step
uint128[] memory newRanges = new uint128[](1);
newRanges[0] = 999e18;
uint128[] memory newPrices = new uint128[](1);
newPrices[0] = 42e15;

factory.updateCurve(newRanges, newPrices);

// Create storyline — should use new curve
vm.prank(writer);
factory.createStoryline("New Curve Story", VALID_CID, FAKE_HASH, false);

// Verify the bond received the new params (check via mock)
assertEq(factory.stepRanges(0), 999e18);
assertEq(factory.stepPrices(0), 42e15);
}
}