Skip to content

Commit 2dd2a41

Browse files
committed
feat(contracts): use ERC721A for Titan 1
1 parent 15a2087 commit 2dd2a41

File tree

6 files changed

+103
-52
lines changed

6 files changed

+103
-52
lines changed

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@
1818
path = contracts/lib/murky
1919
url = https://github.com/dmfxyz/murky
2020
tag = v0.1.0
21+
[submodule "contracts/lib/ERC721A-Upgradeable"]
22+
path = contracts/lib/ERC721A-Upgradeable
23+
url = https://github.com/chiru-labs/ERC721A-Upgradeable
24+
tag = v4.3.0

contracts/.gas-snapshot

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
SuccinctGovernorTest:test_Cancel_WhenValid() (gas: 454202)
2-
SuccinctGovernorTest:test_Execute_WhenSlashNoIndexShift() (gas: 1221270)
2+
SuccinctGovernorTest:test_Execute_WhenSlashNoIndexShift() (gas: 1221312)
33
SuccinctGovernorTest:test_Propose_WhenValid() (gas: 583900)
44
SuccinctGovernorTest:test_RevertCancel_WhenNotProverOwner() (gas: 444114)
55
SuccinctGovernorTest:test_RevertCastVote_WhenNotProverOwner() (gas: 452153)
@@ -155,40 +155,40 @@ SuccinctStakingUnstakeTests:test_Unstake_WhenTwoStakersOneProverReward() (gas: 1
155155
SuccinctStakingUnstakeTests:test_Unstake_WhenTwoStakersTwoProversDispenseOnly() (gas: 1052844)
156156
SuccinctStakingUnstakeTests:test_Unstake_WhenValid() (gas: 654142)
157157
SuccinctTitan1GPUTest:test_BeginFCFSSale() (gas: 125844)
158-
SuccinctTitan1GPUTest:test_BeginSale_FinalizesActiveSale() (gas: 175622)
158+
SuccinctTitan1GPUTest:test_BeginSale_FinalizesActiveSale() (gas: 197855)
159159
SuccinctTitan1GPUTest:test_BeginWhitelistSale() (gas: 125891)
160-
SuccinctTitan1GPUTest:test_CanScheduleSaleAfterCurrentEnds() (gas: 173586)
161-
SuccinctTitan1GPUTest:test_CanScheduleSaleAfterCurrentEndsWithBuffer() (gas: 173519)
162-
SuccinctTitan1GPUTest:test_CanStartSaleAfterPreviousSaleExpired() (gas: 173452)
160+
SuccinctTitan1GPUTest:test_CanScheduleSaleAfterCurrentEnds() (gas: 195819)
161+
SuccinctTitan1GPUTest:test_CanScheduleSaleAfterCurrentEndsWithBuffer() (gas: 195752)
162+
SuccinctTitan1GPUTest:test_CanStartSaleAfterPreviousSaleExpired() (gas: 195685)
163163
SuccinctTitan1GPUTest:test_CanStartSaleWhenNoPreviousSale() (gas: 121879)
164-
SuccinctTitan1GPUTest:test_DecreaseAllocationAfterMinting() (gas: 2872141)
165-
SuccinctTitan1GPUTest:test_FcfsMint() (gas: 279111)
166-
SuccinctTitan1GPUTest:test_FullWorkflow() (gas: 57369937)
167-
SuccinctTitan1GPUTest:test_IncreaseAllocationAfterMinting() (gas: 3166285)
168-
SuccinctTitan1GPUTest:test_MintExactlyToIncreasedAllocation() (gas: 3182322)
169-
SuccinctTitan1GPUTest:test_Mint_Batch() (gas: 454818)
170-
SuccinctTitan1GPUTest:test_Mint_MultipleUsers() (gas: 348465)
171-
SuccinctTitan1GPUTest:test_Mint_Single() (gas: 334134)
172-
SuccinctTitan1GPUTest:test_MultipleAllocationUpdatesActiveSale() (gas: 2451988)
173-
SuccinctTitan1GPUTest:test_PermitAuctionPriceDrop() (gas: 348339)
174-
SuccinctTitan1GPUTest:test_PermitFCFSMint() (gas: 312629)
175-
SuccinctTitan1GPUTest:test_PermitWhitelistMint() (gas: 434152)
164+
SuccinctTitan1GPUTest:test_DecreaseAllocationAfterMinting() (gas: 802544)
165+
SuccinctTitan1GPUTest:test_FcfsMint() (gas: 300500)
166+
SuccinctTitan1GPUTest:test_FullWorkflow() (gas: 9922563)
167+
SuccinctTitan1GPUTest:test_IncreaseAllocationAfterMinting() (gas: 859198)
168+
SuccinctTitan1GPUTest:test_MintExactlyToIncreasedAllocation() (gas: 875178)
169+
SuccinctTitan1GPUTest:test_Mint_Batch() (gas: 392431)
170+
SuccinctTitan1GPUTest:test_Mint_MultipleUsers() (gas: 369357)
171+
SuccinctTitan1GPUTest:test_Mint_Single() (gas: 355523)
172+
SuccinctTitan1GPUTest:test_MultipleAllocationUpdatesActiveSale() (gas: 738724)
173+
SuccinctTitan1GPUTest:test_PermitAuctionPriceDrop() (gas: 369704)
174+
SuccinctTitan1GPUTest:test_PermitFCFSMint() (gas: 334018)
175+
SuccinctTitan1GPUTest:test_PermitWhitelistMint() (gas: 413131)
176176
SuccinctTitan1GPUTest:test_RevertBeginFCFSSale_InvalidPriceConfig() (gas: 24247)
177177
SuccinctTitan1GPUTest:test_RevertBeginWhitelistSale_InvalidPriceConfig() (gas: 24282)
178178
SuccinctTitan1GPUTest:test_RevertFCFSMint_MultipleTokens() (gas: 157409)
179179
SuccinctTitan1GPUTest:test_RevertNonAuctioneer_beginSale() (gas: 23960)
180180
SuccinctTitan1GPUTest:test_RevertNonOwner_setBaseURI() (gas: 18921)
181181
SuccinctTitan1GPUTest:test_RevertNonOwner_setWhitelistAllocation() (gas: 21103)
182182
SuccinctTitan1GPUTest:test_RevertNonOwner_withdraw() (gas: 19080)
183-
SuccinctTitan1GPUTest:test_RevertPermitAndWhitelistMint_WhenDeadlineExpired() (gas: 318976)
184-
SuccinctTitan1GPUTest:test_RevertPermitMint_PermitValueTooLow() (gas: 315695)
185-
SuccinctTitan1GPUTest:test_RevertSetAllocationBelowMintedAmount() (gas: 2563180)
186-
SuccinctTitan1GPUTest:test_RevertSetAllocationToZeroAfterMinting() (gas: 1143912)
183+
SuccinctTitan1GPUTest:test_RevertPermitAndWhitelistMint_WhenDeadlineExpired() (gas: 340355)
184+
SuccinctTitan1GPUTest:test_RevertPermitMint_PermitValueTooLow() (gas: 337074)
185+
SuccinctTitan1GPUTest:test_RevertSetAllocationBelowMintedAmount() (gas: 707899)
186+
SuccinctTitan1GPUTest:test_RevertSetAllocationToZeroAfterMinting() (gas: 476394)
187187
SuccinctTitan1GPUTest:test_RevertSetWhitelistAllocation_ZeroAddress() (gas: 18496)
188188
SuccinctTitan1GPUTest:test_RevertSetWhitelistAllocationsBatch_ArrayLengthMismatch() (gas: 24379)
189189
SuccinctTitan1GPUTest:test_RevertSetWhitelistAllocationsBatch_ZeroAddress() (gas: 48658)
190-
SuccinctTitan1GPUTest:test_RevertTokenURI_WhenTokenDoesNotExist() (gas: 28954)
191-
SuccinctTitan1GPUTest:test_RevertTransferFrom_TokenIsSoulbound() (gas: 342662)
190+
SuccinctTitan1GPUTest:test_RevertTokenURI_WhenTokenDoesNotExist() (gas: 28230)
191+
SuccinctTitan1GPUTest:test_RevertTransferFrom_TokenIsSoulbound() (gas: 366512)
192192
SuccinctTitan1GPUTest:test_RevertWhitelistMint_ExceedsBatch() (gas: 190019)
193193
SuccinctTitan1GPUTest:test_RevertWhitelistMint_ExceedsBatchSize() (gas: 189352)
194194
SuccinctTitan1GPUTest:test_RevertWhitelistMint_NoAllocation() (gas: 159416)
@@ -197,14 +197,14 @@ SuccinctTitan1GPUTest:test_RevertWhitelistMint_ZeroAmount() (gas: 125984)
197197
SuccinctTitan1GPUTest:test_RevertWithdraw_InvalidPayment() (gas: 28154)
198198
SuccinctTitan1GPUTest:test_RevertWithdraw_ZeroAddress() (gas: 18473)
199199
SuccinctTitan1GPUTest:test_RevertWithdraw_ZeroAmount() (gas: 20516)
200-
SuccinctTitan1GPUTest:test_SetAllocationToExactlyMintedAmount() (gas: 2457153)
201-
SuccinctTitan1GPUTest:test_SetBaseURI() (gas: 338857)
202-
SuccinctTitan1GPUTest:test_SetUp() (gas: 43010)
200+
SuccinctTitan1GPUTest:test_SetAllocationToExactlyMintedAmount() (gas: 720649)
201+
SuccinctTitan1GPUTest:test_SetBaseURI() (gas: 360412)
202+
SuccinctTitan1GPUTest:test_SetUp() (gas: 42973)
203203
SuccinctTitan1GPUTest:test_SetWhitelistAllocation() (gas: 50249)
204204
SuccinctTitan1GPUTest:test_SetWhitelistAllocationsBatch() (gas: 117646)
205205
SuccinctTitan1GPUTest:test_SetWhitelistAllocationsBatch_AfterSaleStart() (gas: 156263)
206-
SuccinctTitan1GPUTest:test_TokenURI() (gas: 346876)
207-
SuccinctTitan1GPUTest:test_Withdraw() (gas: 558254)
206+
SuccinctTitan1GPUTest:test_TokenURI() (gas: 368431)
207+
SuccinctTitan1GPUTest:test_Withdraw() (gas: 484325)
208208
SuccinctTokenTest:test_InitialOwner() (gas: 12808)
209209
SuccinctTokenTest:test_Mint_WhenOwner() (gas: 61178)
210210
SuccinctTokenTest:test_RevertMint_WhenNotOwner() (gas: 11221)
@@ -238,10 +238,10 @@ SuccinctVAppRewardsTest:test_IsClaimed_BitMapLogic() (gas: 219830)
238238
SuccinctVAppRewardsTest:test_RevertRewardClaim_WhenAfterDeadline() (gas: 89373)
239239
SuccinctVAppRewardsTest:test_RevertRewardClaim_WhenAlreadyClaimed() (gas: 631653)
240240
SuccinctVAppRewardsTest:test_RevertRewardClaim_WhenEmptyProof() (gas: 129650)
241-
SuccinctVAppRewardsTest:test_RevertRewardClaim_WhenInvalidProof() (gas: 155554)
241+
SuccinctVAppRewardsTest:test_RevertRewardClaim_WhenInvalidProof() (gas: 155530)
242242
SuccinctVAppRewardsTest:test_RevertRewardClaim_WhenPaused() (gas: 138531)
243243
SuccinctVAppRewardsTest:test_RevertRewardClaim_WhenWrongAccount() (gas: 152658)
244-
SuccinctVAppRewardsTest:test_RevertRewardClaim_WhenWrongAmount() (gas: 155663)
244+
SuccinctVAppRewardsTest:test_RevertRewardClaim_WhenWrongAmount() (gas: 155639)
245245
SuccinctVAppRewardsTest:test_RevertSetRewardRoot_WhenBeforeDeadline() (gas: 70205)
246246
SuccinctVAppRewardsTest:test_RevertSetRewardRoot_WhenNotAuctioneer() (gas: 30986)
247247
SuccinctVAppRewardsTest:test_RewardClaim_MultipleRoots() (gas: 315217)

contracts/foundry.lock

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"lib/sp1-contracts": {
3+
"rev": "512b5e029abc27f6e46a3c7eba220dac83ecc306"
4+
},
5+
"lib/ERC721A-Upgradeable": {
6+
"tag": {
7+
"name": "v4.3.0",
8+
"rev": "4508a3d65ed75ccab59e80f25571c2818c03f55b"
9+
}
10+
},
11+
"lib/forge-std": {
12+
"rev": "8f24d6b04c92975e0795b5868aa0d783251cdeaa"
13+
},
14+
"lib/murky": {
15+
"rev": "5feccd1253d7da820f7cccccdedf64471025455d"
16+
},
17+
"lib/openzeppelin-contracts": {
18+
"rev": "acd4ff74de833399287ed6b31b4debf6b2b35527"
19+
},
20+
"lib/openzeppelin-contracts-upgradeable": {
21+
"rev": "3d5fa5c24c411112bab47bec25cfa9ad0af0e6e8"
22+
}
23+
}

contracts/lib/ERC721A-Upgradeable

Submodule ERC721A-Upgradeable added at 4508a3d

contracts/src/tokens/SuccinctTitan1GPU.sol

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ import {UUPSUpgradeable} from
1010
"../../lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
1111
import {ReentrancyGuardUpgradeable} from
1212
"../../lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol";
13-
import {ERC721Upgradeable} from
14-
"../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC721/ERC721Upgradeable.sol";
13+
import {ERC721AUpgradeable} from "../../lib/ERC721A-Upgradeable/contracts/ERC721AUpgradeable.sol";
14+
import {IERC721AUpgradeable} from "../../lib/ERC721A-Upgradeable/contracts/IERC721AUpgradeable.sol";
15+
import {ERC721A__Initializable} from
16+
"../../lib/ERC721A-Upgradeable/contracts/ERC721A__Initializable.sol";
17+
import {ERC721AStorage} from "../../lib/ERC721A-Upgradeable/contracts/ERC721AStorage.sol";
1518
import {IERC20} from "../../lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
1619
import {IERC20Permit} from
1720
"../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol";
@@ -26,13 +29,15 @@ string constant SYMBOL = "Titan1GPU";
2629
contract SuccinctTitan1GPU is
2730
ISuccinctTitan1GPU,
2831
Initializable,
32+
ERC721A__Initializable,
2933
OwnableUpgradeable,
30-
ERC721Upgradeable,
34+
ERC721AUpgradeable,
3135
ReentrancyGuardUpgradeable,
3236
UUPSUpgradeable
3337
{
3438
using SafeERC20 for IERC20;
3539
using Strings for uint256;
40+
using ERC721AStorage for ERC721AStorage.Layout;
3641

3742
/*//////////////////////////////////////////////////////////////
3843
STORAGE
@@ -47,8 +52,8 @@ contract SuccinctTitan1GPU is
4752
/// @inheritdoc ISuccinctTitan1GPU
4853
string public override baseURI;
4954

50-
/// @inheritdoc ISuccinctTitan1GPU
51-
uint256 public override totalSupply;
55+
/// @dev Track actual minted supply since ERC721A's totalSupply includes skipped tokens
56+
uint256 private _actualSupply;
5257

5358
/// @inheritdoc ISuccinctTitan1GPU
5459
uint256 public override nextId;
@@ -66,7 +71,7 @@ contract SuccinctTitan1GPU is
6671
mapping(address => uint256) public override whitelistMinted;
6772

6873
/// @dev Reserved storage gap for future upgrades.
69-
uint256[50] internal __gap;
74+
uint256[49] internal __gap;
7075

7176
/*//////////////////////////////////////////////////////////////
7277
MODIFIERS
@@ -94,6 +99,7 @@ contract SuccinctTitan1GPU is
9499
function initialize(address _owner, address _auctioneer, address _usdc, string memory _uri)
95100
external
96101
initializer
102+
initializerERC721A
97103
{
98104
// Ensure that parameters critical for functionality are non-zero.
99105
if (_owner == address(0) || _auctioneer == address(0) || _usdc == address(0)) {
@@ -103,7 +109,7 @@ contract SuccinctTitan1GPU is
103109
// Set initial state.
104110
__UUPSUpgradeable_init();
105111
__Ownable_init(_owner);
106-
__ERC721_init(NAME, SYMBOL);
112+
__ERC721A_init(NAME, SYMBOL);
107113
__ReentrancyGuard_init();
108114
_updateAuctioneer(_auctioneer);
109115
usdc = _usdc;
@@ -118,13 +124,23 @@ contract SuccinctTitan1GPU is
118124
function tokenURI(uint256 _tokenId)
119125
public
120126
view
121-
override(ERC721Upgradeable, ISuccinctTitan1GPU)
127+
override(ERC721AUpgradeable, ISuccinctTitan1GPU)
122128
returns (string memory)
123129
{
124-
_requireOwned(_tokenId);
130+
if (!_exists(_tokenId)) revert IERC721AUpgradeable.URIQueryForNonexistentToken();
125131
return string(abi.encodePacked(baseURI, _tokenId.toString()));
126132
}
127133

134+
/// @inheritdoc ISuccinctTitan1GPU
135+
function totalSupply()
136+
public
137+
view
138+
override(ERC721AUpgradeable, ISuccinctTitan1GPU)
139+
returns (uint256)
140+
{
141+
return _actualSupply;
142+
}
143+
128144
/// @inheritdoc ISuccinctTitan1GPU
129145
function currentPrice() external view override returns (uint256) {
130146
return _currentPrice();
@@ -196,6 +212,8 @@ contract SuccinctTitan1GPU is
196212
if (_isSaleActive()) {
197213
uint256 skipped = currentSale.batchSize - currentSale.sold;
198214
nextId += skipped;
215+
// Update ERC721A's internal counter to match our nextId
216+
ERC721AStorage.layout()._currentIndex = nextId;
199217
emit SaleFinalized(currentSale.saleId, skipped);
200218
}
201219

@@ -373,16 +391,15 @@ contract SuccinctTitan1GPU is
373391
uint256 startId = nextId;
374392
unchecked {
375393
nextId += _amount;
376-
totalSupply += _amount;
394+
_actualSupply += _amount;
377395
}
378396

397+
// Single batch mint call
398+
_mint(_to, _amount);
399+
400+
// Emit individual Mint events for compatibility
379401
for (uint256 i = 0; i < _amount; ++i) {
380-
// Get the next token ID.
381402
uint256 tokenId = startId + i;
382-
383-
// Mint the token.
384-
_safeMint(_to, tokenId);
385-
386403
emit Mint(_saleId, _to, tokenId, _saleType, _unitPrice);
387404
}
388405
}
@@ -411,18 +428,23 @@ contract SuccinctTitan1GPU is
411428
return baseURI;
412429
}
413430

431+
/// @dev Returns the starting token ID for sequential mints.
432+
function _startTokenId() internal view virtual override returns (uint256) {
433+
return 0;
434+
}
435+
414436
/// @dev Prevents transfers after minting (soulbound).
415-
function _update(address _to, uint256 _tokenId, address _auth)
437+
function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity)
416438
internal
417439
virtual
418-
override(ERC721Upgradeable)
419-
returns (address)
440+
override
420441
{
421-
address from = _ownerOf(_tokenId);
442+
// Allow minting (from == address(0))
443+
// Prevent transfers (from != address(0))
422444
if (from != address(0)) {
423445
revert TokenIsSoulbound();
424446
}
425-
return super._update(_to, _tokenId, _auth);
447+
super._beforeTokenTransfers(from, to, startTokenId, quantity);
426448
}
427449

428450
/// @dev Standard UUPS authorization gate.

contracts/test/SuccinctTitan1GPU.t.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {IERC20} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC20.
77
import {IERC20Permit} from
88
"../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol";
99
import {IERC721Errors} from "../lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol";
10+
import {IERC721AUpgradeable} from "../lib/ERC721A-Upgradeable/contracts/IERC721AUpgradeable.sol";
1011
import {ERC1967Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";
1112
import {OwnableUpgradeable} from
1213
"../lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
@@ -1401,11 +1402,11 @@ contract SuccinctTitan1GPUTest is Test {
14011402

14021403
function test_RevertTokenURI_WhenTokenDoesNotExist() public {
14031404
// Try to get tokenURI for a non-existent token
1404-
vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, 999));
1405+
vm.expectRevert(IERC721AUpgradeable.URIQueryForNonexistentToken.selector);
14051406
SuccinctTitan1GPU(nft).tokenURI(999);
14061407

14071408
// Also test with token ID 0 when no tokens have been minted
1408-
vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, 0));
1409+
vm.expectRevert(IERC721AUpgradeable.URIQueryForNonexistentToken.selector);
14091410
SuccinctTitan1GPU(nft).tokenURI(0);
14101411
}
14111412

0 commit comments

Comments
 (0)