Skip to content

Commit 23d75ce

Browse files
Add block number minting cutoff
1 parent 950fb36 commit 23d75ce

File tree

5 files changed

+122
-1
lines changed

5 files changed

+122
-1
lines changed

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ solc = "0.8.30"
66

77
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
88
optimizer = true
9-
optimizer_runs = 200
9+
optimizer_runs = 800
1010
via_ir = true

src/Fraxiversarry.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ contract Fraxiversarry is
5959
uint256 public giftMintingLimit;
6060
uint256 public giftMintingPrice;
6161
uint256 public mintingFeeBasisPoints;
62+
uint256 public mintingCutoffBlock;
6263

6364
bool private _isBridgeOperation;
6465

@@ -83,6 +84,7 @@ contract Fraxiversarry is
8384
nextGiftTokenId = mintingLimit;
8485
nextPremiumTokenId = mintingLimit + giftMintingLimit;
8586
mintingFeeBasisPoints = 25; // 0.25%
87+
mintingCutoffBlock = block.number + (35 days / 2 seconds); // Approximately 5 weeks with 2s blocktime
8688

8789
//TODO: Set correct URIs
8890
giftTokenUri = "https://gift.tba.frax/";
@@ -102,6 +104,7 @@ contract Fraxiversarry is
102104
}
103105

104106
function paidMint(address erc20Contract) public returns (uint256) {
107+
if (block.number > mintingCutoffBlock) revert MintingPeriodOver();
105108
if (nextTokenId >= mintingLimit) revert MintingLimitReached();
106109
if (mintPrices[erc20Contract] == 0) revert UnsupportedToken();
107110

@@ -122,6 +125,7 @@ contract Fraxiversarry is
122125
}
123126

124127
function giftMint(address recipient) public returns (uint256) {
128+
if (block.number > mintingCutoffBlock) revert MintingPeriodOver();
125129
if (nextGiftTokenId >= mintingLimit + giftMintingLimit) revert GiftMintingLimitReached();
126130

127131
uint256 tokenId = nextGiftTokenId;
@@ -311,6 +315,14 @@ contract Fraxiversarry is
311315
emit MintingFeeUpdated(previousFeeBasisPoints, newFeeBasisPoints);
312316
}
313317

318+
function updateMintingCutoffBlock(uint256 newCutoffBlock) public onlyOwner {
319+
uint256 previousCutoffBlock = mintingCutoffBlock;
320+
321+
mintingCutoffBlock = newCutoffBlock;
322+
323+
emit MintingCutoffBlockUpdated(previousCutoffBlock, newCutoffBlock);
324+
}
325+
314326
function retrieveCollectedFees(address erc20Contract, address to) public onlyOwner {
315327
uint256 feeAmount = collectedFees[erc20Contract];
316328
if (feeAmount == 0) return;

src/interfaces/IFraxiversarryErrors.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ contract IFraxiversarryErrors {
1212
error InvalidGiftMintPrice();
1313
error InvalidRange();
1414
error MintingLimitReached();
15+
error MintingPeriodOver();
1516
error MissingComposedMessage();
1617
error OnlyTokenOwnerCanBurnTheToken();
1718
error OnlyTokenOwnerCanFuseTokens();

src/interfaces/IFraxiversarryEvents.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ contract IFraxiversarryEvents {
1010

1111
event GiftMintPriceUpdated(uint256 previousMintPrice, uint256 newMintPrice);
1212

13+
event MintingCutoffBlockUpdated(uint256 previousCutoffBlock, uint256 newCutoffBlock);
14+
1315
event MintingFeeUpdated(uint256 previousFeeBasisPoints, uint256 newFeeBasisPoints);
1416

1517
event MintPriceUpdated(address indexed erc20Contract, uint256 previousMintPrice, uint256 newMintPrice);

test/Fraxiversarry.t.sol

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,112 @@ contract FraxiversarryTest is Test, IFraxiversarryErrors, IFraxiversarryEvents {
15191519
fraxiversarry.tokenURI(tokenId);
15201520
}
15211521

1522+
// ----------------------------------------------------------
1523+
// Minting cutoff block tests
1524+
// ----------------------------------------------------------
1525+
1526+
function testConstructorSetsMintingCutoffBlockRelativeToDeployBlock() public {
1527+
// Redeploy locally to assert constructor math precisely
1528+
MockLzEndpoint localEndpoint = new MockLzEndpoint();
1529+
1530+
uint256 deployBlock = block.number;
1531+
Fraxiversarry local = new Fraxiversarry(owner, address(localEndpoint));
1532+
1533+
uint256 expectedDelta = (35 days / 2 seconds);
1534+
assertEq(local.mintingCutoffBlock(), deployBlock + expectedDelta);
1535+
}
1536+
1537+
function testPaidMintAllowedAtCutoffBlock() public {
1538+
// Set cutoff to current block so mint is still allowed
1539+
vm.prank(owner);
1540+
fraxiversarry.updateMintingCutoffBlock(block.number);
1541+
1542+
_approveWithFee(alice, wfrax);
1543+
1544+
vm.prank(alice);
1545+
uint256 tokenId = fraxiversarry.paidMint(address(wfrax));
1546+
1547+
assertEq(fraxiversarry.ownerOf(tokenId), alice);
1548+
assertEq(uint256(fraxiversarry.tokenTypes(tokenId)), uint256(Fraxiversarry.TokenType.BASE));
1549+
}
1550+
1551+
function testPaidMintRevertsAfterCutoffBlock() public {
1552+
uint256 cutoff = block.number;
1553+
vm.prank(owner);
1554+
fraxiversarry.updateMintingCutoffBlock(cutoff);
1555+
1556+
// Move to cutoff + 1
1557+
vm.roll(cutoff + 1);
1558+
1559+
_approveWithFee(alice, wfrax);
1560+
1561+
vm.prank(alice);
1562+
vm.expectRevert(MintingPeriodOver.selector);
1563+
fraxiversarry.paidMint(address(wfrax));
1564+
}
1565+
1566+
function testGiftMintAllowedAtCutoffBlock() public {
1567+
vm.prank(owner);
1568+
fraxiversarry.updateMintingCutoffBlock(block.number);
1569+
1570+
(,, uint256 giftMintingPrice) = fraxiversarry.getGiftMintingPriceWithFee();
1571+
vm.prank(alice);
1572+
wfrax.approve(address(fraxiversarry), giftMintingPrice);
1573+
1574+
vm.prank(alice);
1575+
uint256 tokenId = fraxiversarry.giftMint(bob);
1576+
1577+
assertEq(fraxiversarry.ownerOf(tokenId), bob);
1578+
assertEq(uint256(fraxiversarry.tokenTypes(tokenId)), uint256(Fraxiversarry.TokenType.GIFT));
1579+
}
1580+
1581+
function testGiftMintRevertsAfterCutoffBlock() public {
1582+
uint256 cutoff = block.number;
1583+
vm.prank(owner);
1584+
fraxiversarry.updateMintingCutoffBlock(cutoff);
1585+
1586+
vm.roll(cutoff + 1);
1587+
1588+
(,, uint256 giftMintingPrice) = fraxiversarry.getGiftMintingPriceWithFee();
1589+
vm.prank(alice);
1590+
wfrax.approve(address(fraxiversarry), giftMintingPrice);
1591+
1592+
vm.prank(alice);
1593+
vm.expectRevert(MintingPeriodOver.selector);
1594+
fraxiversarry.giftMint(bob);
1595+
}
1596+
1597+
function testUpdateMintingCutoffBlockOnlyOwner() public {
1598+
vm.prank(alice);
1599+
vm.expectRevert(); // Ownable: caller is not the owner
1600+
fraxiversarry.updateMintingCutoffBlock(block.number + 100);
1601+
}
1602+
1603+
function testUpdateMintingCutoffBlockEmitsEvent() public {
1604+
uint256 previous = fraxiversarry.mintingCutoffBlock();
1605+
uint256 next = previous + 123;
1606+
1607+
vm.recordLogs();
1608+
vm.prank(owner);
1609+
fraxiversarry.updateMintingCutoffBlock(next);
1610+
Vm.Log[] memory logs = vm.getRecordedLogs();
1611+
1612+
bytes32 expectedSig = keccak256("MintingCutoffBlockUpdated(uint256,uint256)");
1613+
bool found;
1614+
1615+
for (uint256 i; i < logs.length; ++i) {
1616+
if (logs[i].topics[0] == expectedSig) {
1617+
found = true;
1618+
(uint256 loggedPrev, uint256 loggedNext) = abi.decode(logs[i].data, (uint256, uint256));
1619+
assertEq(loggedPrev, previous);
1620+
assertEq(loggedNext, next);
1621+
}
1622+
}
1623+
1624+
assertTrue(found, "MintingCutoffBlockUpdated event not found");
1625+
assertEq(fraxiversarry.mintingCutoffBlock(), next);
1626+
}
1627+
15221628
// ----------------------------------------------------------
15231629
// ONFT view helpers (token() / approvalRequired())
15241630
// ----------------------------------------------------------

0 commit comments

Comments
 (0)