Skip to content

Enable address(0) erc20 balances to match ethscription behavior#121

Merged
RogerPodacter merged 3 commits into
evm-backend-demofrom
fix_erc20
Oct 16, 2025
Merged

Enable address(0) erc20 balances to match ethscription behavior#121
RogerPodacter merged 3 commits into
evm-backend-demofrom
fix_erc20

Conversation

@RogerPodacter

@RogerPodacter RogerPodacter commented Oct 16, 2025

Copy link
Copy Markdown
Member

Note

Introduce an upgradeable ERC20 base that allows address(0) balances and migrate EthscriptionsERC20 to use it, enabling mint/transfer to the null address with updated tests and interface paths.

  • Contracts:
    • ERC20NullOwnerCappedUpgradeable (new): Upgradeable ERC20 with cap and storage layout that treats address(0) as a valid holder; includes maxSupply().
    • EthscriptionsERC20: Refactored to extend ERC20NullOwnerCappedUpgradeable; TokenManager-only mint and forceTransfer now allow to/from == address(0) via _update; disables user transfers/approvals; exposes maxSupply() instead of cap().
    • TokenManager/Ethscriptions/CollectionsManager/EthscriptionERC721: Update imports to interfaces/IProtocolHandler and local paths; align with null-owner ERC20 behavior.
  • Tests:
    • Replace cap() assertions with maxSupply(); add tests for minting to and transferring NFTs into address(0) reflecting in ERC20 balances; adjust imports.

Written by Cursor Bugbot for commit 9d82ffe. This will update automatically on new commits. Configure here.

@RogerPodacter RogerPodacter requested a review from Copilot October 16, 2025 21:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request enables ERC-20 tokens to have balances at the zero address (address(0)) to match Ethscription behavior, where tokens can be held by the null address.

Key changes:

  • Creates a new ERC20 implementation that allows null address ownership
  • Updates token interface from cap() to maxSupply() for consistency
  • Adds comprehensive test coverage for null address token operations

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
contracts/src/ERC20NullOwnerCappedUpgradeable.sol New ERC20 implementation that supports null address ownership by overriding the _update function to credit rather than burn tokens sent to address(0)
contracts/src/EthscriptionsERC20.sol Refactored to use the new null-owner-aware base contract and simplified token management functions
contracts/test/EthscriptionsToken.t.sol Added tests for null address minting and transfers, plus updated existing test to use new maxSupply() method

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread contracts/src/EthscriptionsERC20.sol Outdated
// TokenManager-only mint that allows to == address(0)
function mint(address to, uint256 amount) external onlyTokenManager {
_mint(to, amount);
_update(address(0), to, amount);

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mint function bypasses the standard _mint helper function which handles minting logic. Consider using _mint(to, amount) instead of directly calling _update(address(0), to, amount) for consistency with ERC20 patterns and to ensure any future mint-specific logic is preserved.

Suggested change
_update(address(0), to, amount);
_mint(to, amount);

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +29
bytes32 private constant STORAGE_LOCATION = 0x8f4f7bb0f9a741a04db8c5a3930ef1872dc1b0c6f996f78adc3f57e5f8b78400;

function _getS() private pure returns (TokenStorage storage $) {
assembly {
$.slot := STORAGE_LOCATION

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The storage location calculation comment should be verified. The actual hash calculation should match the provided constant value to ensure storage slot uniqueness and prevent collisions with other contracts.

Suggested change
bytes32 private constant STORAGE_LOCATION = 0x8f4f7bb0f9a741a04db8c5a3930ef1872dc1b0c6f996f78adc3f57e5f8b78400;
function _getS() private pure returns (TokenStorage storage $) {
assembly {
$.slot := STORAGE_LOCATION
// Compute storage slot as described in the comment above
function _storageLocation() internal pure returns (bytes32) {
// keccak256(abi.encode(uint256(keccak256("ethscriptions.storage.ERC20NullOwnerCapped")) - 1)) & ~bytes32(uint256(0xff))
bytes32 typeHash = keccak256("ethscriptions.storage.ERC20NullOwnerCapped");
uint256 slot = uint256(keccak256(abi.encode(uint256(typeHash) - 1))) & ~uint256(0xff);
return bytes32(slot);
}
function _getS() private pure returns (TokenStorage storage $) {
bytes32 location = _storageLocation();
assembly {
$.slot := location

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +156
// Mint (null-owner aware)
function _mint(address account, uint256 value) internal {
_update(address(0), account, value);
}

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _mint function is defined but not used in the codebase where mint directly calls _update. This creates inconsistency - either use _mint in the public mint function or remove this unused helper to avoid confusion.

Suggested change
// Mint (null-owner aware)
function _mint(address account, uint256 value) internal {
_update(address(0), account, value);
}

Copilot uses AI. Check for mistakes.
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

@RogerPodacter RogerPodacter requested a review from Copilot October 16, 2025 21:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +122 to +133
// Debit from
uint256 fromBalance = $.balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
$.balances[from] = fromBalance - value;
}
// Credit to
unchecked {
$.balances[to] += value;
}

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _update function doesn't handle the case where from is address(0) (minting scenario). When minting, from should be address(0) and the balance check will fail since address(0) has no balance to debit. This function should handle minting by checking if from == address(0) and only updating totalSupply and crediting to in that case.

Suggested change
// Debit from
uint256 fromBalance = $.balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
$.balances[from] = fromBalance - value;
}
// Credit to
unchecked {
$.balances[to] += value;
}
if (from == address(0)) {
// Minting: only credit 'to'
unchecked {
$.balances[to] += value;
}
} else {
// Debit from
uint256 fromBalance = $.balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
$.balances[from] = fromBalance - value;
}
// Credit to
unchecked {
$.balances[to] += value;
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +151
function _mint(address account, uint256 value) internal {
TokenStorage storage $ = _getS();

uint256 newSupply = $.totalSupply + value;
if (newSupply > $.cap) {
revert ERC20ExceededCap(newSupply, $.cap);
}

$.totalSupply = newSupply;

unchecked { $.balances[account] += value; }

emit Transfer(address(0), account, value);
}

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _mint function bypasses the _update function and directly manipulates balances and totalSupply. This creates inconsistency since _update is meant to be the single point for balance changes. The _mint function should call _update(address(0), account, value) after checking the cap, but _update needs to be fixed first to handle the minting case.

Copilot uses AI. Check for mistakes.
@RogerPodacter RogerPodacter merged commit d1523b5 into evm-backend-demo Oct 16, 2025
2 checks passed
@RogerPodacter RogerPodacter deleted the fix_erc20 branch October 16, 2025 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants