Skip to content

Commit 83ef4a3

Browse files
committed
Refactor token ID handling and improve metadata functions in ERC20FixedDenomination and ERC404NullOwnerCappedUpgradeable contracts
- Simplified token ID management by removing the encoding and decoding methods, directly using the mint ID in relevant functions. - Enhanced the `tokenURI` function to validate token IDs and streamline metadata generation. - Updated tests to reflect changes in token ID handling and ownership assertions, ensuring consistency with the new implementation.
1 parent 7ab3767 commit 83ef4a3

File tree

3 files changed

+41
-94
lines changed

3 files changed

+41
-94
lines changed

contracts/src/ERC20FixedDenomination.sol

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ contract ERC20FixedDenomination is ERC404NullOwnerCappedUpgradeable {
9090
_transferERC20(from, to, units());
9191

9292
// Transfer the specific NFT using the proper function
93-
uint256 id = _encodeMintId(nftId);
94-
_transferERC721(from, to, id);
93+
_transferERC721(from, to, nftId);
9594
}
9695

9796
// =============================================================
@@ -154,26 +153,14 @@ contract ERC20FixedDenomination is ERC404NullOwnerCappedUpgradeable {
154153

155154
/// @notice Returns metadata URI for NFT tokens
156155
/// @dev Returns a data URI with JSON metadata fetched from the main Ethscriptions contract
157-
function tokenURI(uint256 id_) public view virtual override returns (string memory) {
158-
// Normalize and enforce existence (accepts human mintId or encoded tokenId)
159-
uint256 tokenId = _normalizeTokenId(id_);
160-
ownerOf(tokenId); // reverts on invalid / nonexistent
161-
162-
uint256 mintId = _decodeTokenId(tokenId);
156+
function tokenURI(uint256 mintId) public view virtual override returns (string memory) {
157+
_validateTokenId(mintId);
158+
ownerOf(mintId); // reverts on invalid / nonexistent
163159

164160
// Get the ethscriptionId for this mintId from the manager
165161
ERC20FixedDenominationManager mgr = ERC20FixedDenominationManager(manager);
166162
bytes32 ethscriptionId = mgr.getMintEthscriptionId(deployEthscriptionId, mintId);
167163

168-
if (ethscriptionId == bytes32(0)) {
169-
// If no ethscription found, return minimal metadata
170-
return string(abi.encodePacked(
171-
"data:application/json;utf8,",
172-
'{"name":"', name(), ' Note #', mintId.toString(), '",',
173-
'"description":"Denomination note for ', mintAmount().toString(), ' tokens"}'
174-
));
175-
}
176-
177164
// Get the ethscription data from the main contract
178165
Ethscriptions ethscriptionsContract = Ethscriptions(Predeploys.ETHSCRIPTIONS);
179166
Ethscriptions.Ethscription memory ethscription = ethscriptionsContract.getEthscription(ethscriptionId, false);
@@ -184,7 +171,7 @@ contract ERC20FixedDenomination is ERC404NullOwnerCappedUpgradeable {
184171

185172
// Build the JSON metadata
186173
string memory jsonStart = string.concat(
187-
'{"name":"', name(), ' Note #', mintId.toString(), '"',
174+
'{"name":"', name(), ' Token #', mintId.toString(), '"',
188175
',"description":"Fixed denomination token for ', mintAmount().toString(), ' ', symbol(), ' tokens"'
189176
);
190177

contracts/src/ERC404NullOwnerCappedUpgradeable.sol

Lines changed: 33 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,6 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
6969
/// keccak256(abi.encode(uint256(keccak256("ethscriptions.storage.ERC404NullOwnerCapped")) - 1)) & ~bytes32(uint256(0xff))
7070
bytes32 private constant STORAGE_LOCATION = 0x8a0c9d8e5f7b3a2c1d4e6f8a9b7c5d3e2f1a4b6c8d9e7f5a3b2c1d4e6f8a9b00;
7171

72-
/// @dev Constant for token id encoding
73-
uint256 public constant ID_ENCODING_PREFIX = 1 << 255;
74-
7572
// =============================================================
7673
// EVENTS
7774
// =============================================================
@@ -127,14 +124,9 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
127124
) internal onlyInitializing {
128125
TokenStorage storage $ = _getS();
129126

130-
if (cap_ == 0 || cap_ > ID_ENCODING_PREFIX - 1) {
131-
revert ERC20InvalidCap(cap_);
132-
}
133-
127+
if (cap_ == 0) revert ERC20InvalidCap(cap_);
134128
uint256 base = 10 ** decimals();
135-
if (units_ == 0 || units_ % base != 0) {
136-
revert InvalidUnits(units_);
137-
}
129+
if (units_ == 0 || units_ % base != 0) revert InvalidUnits(units_);
138130

139131
$.name = name_;
140132
$.symbol = symbol_;
@@ -175,6 +167,17 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
175167
TokenStorage storage $ = _getS();
176168
return $.balances[account];
177169
}
170+
171+
function balanceOf(address owner_, uint256 id_)
172+
public
173+
view
174+
returns (uint256)
175+
{
176+
TokenStorage storage $ = _getS();
177+
TokenData storage t = $.tokens[id_];
178+
if (!t.exists) return 0;
179+
return t.owner == owner_ ? 1 : 0;
180+
}
178181

179182
function allowance(address owner, address spender) public view virtual override(IERC404, IERC20) returns (uint256) {
180183
TokenStorage storage $ = _getS();
@@ -204,13 +207,11 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
204207
}
205208

206209
function ownerOf(uint256 id_) public view virtual override(IERC404) returns (address) {
210+
_validateTokenId(id_);
207211
TokenStorage storage $ = _getS();
208-
uint256 tokenId = _normalizeTokenId(id_);
209-
TokenData storage t = $.tokens[tokenId];
212+
TokenData storage t = $.tokens[id_];
210213

211-
if (!t.exists) {
212-
revert NotFound();
213-
}
214+
if (!t.exists) revert NotFound();
214215

215216
return t.owner;
216217
}
@@ -221,14 +222,11 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
221222
}
222223

223224
function getApproved(uint256 id_) public view virtual returns (address) {
225+
_validateTokenId(id_);
224226
TokenStorage storage $ = _getS();
225-
uint256 tokenId = _normalizeTokenId(id_);
227+
if (!$.tokens[id_].exists) revert NotFound();
226228

227-
if (!$.tokens[tokenId].exists) {
228-
revert NotFound();
229-
}
230-
231-
return $.getApproved[tokenId];
229+
return $.getApproved[id_];
232230
}
233231

234232
function isApprovedForAll(address owner_, address operator_) public view virtual override(IERC404) returns (bool) {
@@ -352,9 +350,6 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
352350
if (newSupply > $.cap) {
353351
revert ERC20ExceededCap(newSupply, $.cap);
354352
}
355-
if (newSupply > ID_ENCODING_PREFIX) {
356-
revert MintLimitReached();
357-
}
358353
$.totalSupply = newSupply;
359354
} else {
360355
// Transfer
@@ -371,29 +366,25 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
371366
$.balances[to_] += value_;
372367
}
373368

374-
emit ERC20Transfer(from_, to_, value_);
369+
emit Transfer(from_, to_, value_);
370+
// emit ERC20Transfer(from_, to_, value_);
375371
}
376372

377373
/// @notice Transfer an ERC721 token
378374
function _transferERC721(address from_, address to_, uint256 id_) internal virtual {
379375
TokenStorage storage $ = _getS();
380-
uint256 tokenId = _normalizeTokenId(id_);
381-
TokenData storage t = $.tokens[tokenId];
376+
TokenData storage t = $.tokens[id_];
382377

383-
if (!t.exists) {
384-
revert NotFound();
385-
}
386-
if (from_ != t.owner) {
387-
revert Unauthorized();
388-
}
378+
if (!t.exists) revert NotFound();
379+
if (from_ != t.owner) revert Unauthorized();
389380

390381
if (from_ != address(0)) {
391382
// Clear approval
392-
delete $.getApproved[tokenId];
383+
delete $.getApproved[id_];
393384

394385
// Remove from sender's owned list
395386
uint256 lastTokenId = $.owned[from_][$.owned[from_].length - 1];
396-
if (lastTokenId != tokenId) {
387+
if (lastTokenId != id_) {
397388
uint256 updatedIndex = t.index;
398389
$.owned[from_][updatedIndex] = lastTokenId;
399390
$.tokens[lastTokenId].index = uint88(updatedIndex);
@@ -408,9 +399,9 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
408399
}
409400
t.owner = to_;
410401
t.index = uint88(newIndex);
411-
$.owned[to_].push(tokenId);
402+
$.owned[to_].push(id_);
412403

413-
emit ERC721Transfer(from_, to_, tokenId);
404+
emit ERC721Transfer(from_, to_, id_);
414405
}
415406

416407
/// @notice Mint ERC20 tokens without triggering NFT creation
@@ -426,24 +417,19 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
426417
if (to_ == address(0)) {
427418
revert InvalidRecipient();
428419
}
429-
if (nftId_ == 0 || nftId_ >= ID_ENCODING_PREFIX - 1) {
430-
revert InvalidTokenId();
431-
}
420+
_validateTokenId(nftId_);
432421

433422
TokenStorage storage $ = _getS();
434423

435-
// Add the ID_ENCODING_PREFIX to the provided ID
436-
uint256 id = _encodeMintId(nftId_);
437-
438-
TokenData storage t = $.tokens[id];
424+
TokenData storage t = $.tokens[nftId_];
439425

440426
// Check if this NFT already exists (including null-owner)
441427
if (t.exists) {
442428
revert AlreadyExists();
443429
}
444430

445431
t.exists = true;
446-
_transferERC721(address(0), to_, id);
432+
_transferERC721(address(0), to_, nftId_);
447433

448434
// Increment minted supply counter
449435
$.minted++;
@@ -453,37 +439,11 @@ abstract contract ERC404NullOwnerCappedUpgradeable is
453439
// HELPER FUNCTIONS
454440
// =============================================================
455441

456-
/// @dev Normalizes caller input into the encoded tokenId form (adds prefix for human mint IDs).
457-
function _normalizeTokenId(uint256 id_) internal pure returns (uint256 tokenId_) {
442+
/// @dev Simple tokenId validation: nonzero and not max uint256.
443+
function _validateTokenId(uint256 id_) internal pure {
458444
if (id_ == 0 || id_ == type(uint256).max) {
459445
revert InvalidTokenId();
460446
}
461-
462-
tokenId_ = id_ < ID_ENCODING_PREFIX ? _encodeMintId(id_) : id_;
463-
464-
if (tokenId_ <= ID_ENCODING_PREFIX || tokenId_ == type(uint256).max) {
465-
revert InvalidTokenId();
466-
}
467-
}
468-
469-
/// @dev Encodes a human-friendly mintId into the full tokenId with prefix.
470-
function _encodeMintId(uint256 mintId_) internal pure returns (uint256) {
471-
if (mintId_ == 0 || mintId_ >= ID_ENCODING_PREFIX) {
472-
revert InvalidTokenId();
473-
}
474-
uint256 tokenId = ID_ENCODING_PREFIX + mintId_;
475-
if (tokenId == type(uint256).max) {
476-
revert InvalidTokenId();
477-
}
478-
return tokenId;
479-
}
480-
481-
/// @dev Decodes an encoded tokenId back to the human-friendly mintId.
482-
function _decodeTokenId(uint256 tokenId_) internal pure returns (uint256) {
483-
if (tokenId_ <= ID_ENCODING_PREFIX || tokenId_ == type(uint256).max) {
484-
revert InvalidTokenId();
485-
}
486-
return tokenId_ - ID_ENCODING_PREFIX;
487447
}
488448

489449
// =============================================================

contracts/test/ERC404FixedDenominationNullOwner.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,13 @@ contract ERC404FixedDenominationNullOwnerTest is TestSetup {
108108
// Mint to bob
109109
mintNote(tokenAddr, "TNULL", 1, 1000, bytes32(uint256(0xAAAA)), bob);
110110
assertEq(token.balanceOf(bob), 1000 * 1e18);
111-
assertEq(token.ownerOf(token.ID_ENCODING_PREFIX() + 1), bob);
111+
assertEq(token.ownerOf(1), bob);
112112
assertEq(token.totalSupply(), 1000 * 1e18);
113113

114114
// Mint to null owner (initialOwner = 0) should end with 0x0 owning NFT and ERC20
115115
mintNote(tokenAddr, "TNULL", 2, 1000, bytes32(uint256(0xBBBB)), address(0));
116116
assertEq(token.balanceOf(address(0)), 1000 * 1e18);
117-
assertEq(token.ownerOf(token.ID_ENCODING_PREFIX() + 2), address(0));
117+
assertEq(token.ownerOf(2), address(0));
118118
assertEq(token.totalSupply(), 2000 * 1e18);
119119
}
120120

@@ -132,7 +132,7 @@ contract ERC404FixedDenominationNullOwnerTest is TestSetup {
132132

133133
assertEq(token.totalSupply(), supplyBefore);
134134
assertEq(token.balanceOf(address(0)), 1000 * 1e18);
135-
assertEq(token.ownerOf(token.ID_ENCODING_PREFIX() + 1), address(0));
135+
assertEq(token.ownerOf(1), address(0));
136136
}
137137

138138
function testCapEnforcedOnMint() public {

0 commit comments

Comments
 (0)