Skip to content

feture: refactored ERC6909#284

Open
beebozy wants to merge 4 commits intoPerfect-Abstractions:mainfrom
beebozy:feture-refactor-ERC6909
Open

feture: refactored ERC6909#284
beebozy wants to merge 4 commits intoPerfect-Abstractions:mainfrom
beebozy:feture-refactor-ERC6909

Conversation

@beebozy
Copy link
Contributor

@beebozy beebozy commented Mar 1, 2026

Summary

Changes Made

Checklist

Before submitting this PR, please ensure:

  • Code follows the Solidity feature ban - No inheritance, constructors, modifiers, public/private variables, external library functions, using for directives, or selfdestruct

  • Code follows Design Principles - Readable, uses diamond storage, favors composition over inheritance

  • Code matches the codebase style - Consistent formatting, documentation, and patterns (e.g. ERC20Facet.sol)

  • Code is formatted with forge fmt

  • Existing tests pass - Run tests to be sure existing tests pass.

  • New tests are optional - If you don't provide tests for new functionality or changes then please create a new issue so this can be assigned to someone.

  • All tests pass - Run forge test and ensure everything works

  • Documentation updated - If applicable, update relevant documentation

Make sure to follow the contributing guidelines.

Additional Notes

@netlify
Copy link

netlify bot commented Mar 1, 2026

👷 Deploy request for compose-diamonds pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 2c34faf

@maxnorm maxnorm requested a review from mudgen March 2, 2026 17:20
@maxnorm maxnorm linked an issue Mar 6, 2026 that may be closed by this pull request
Copy link
Collaborator

@maxnorm maxnorm left a comment

Choose a reason for hiding this comment

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

You can remove the redundant subfolder ERC6909. The new approach make it easier to add new features to a standard by only defining a new folder with the feature name under one main ERC6909 folder

@beebozy
Copy link
Contributor Author

beebozy commented Mar 8, 2026

Will make the changes and revert before the day ends

@beebozy
Copy link
Contributor Author

beebozy commented Mar 9, 2026

Hi sir, @maxnorm could you review again?

}

uint256 fromBalance = s.balanceOf[_from][_id];

Copy link
Collaborator

Choose a reason for hiding this comment

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

  • Update balances after the currentAllowance if (line 93)
  • Missing sufficient balance check
uint256 fromBalance = s.balanceOf[_from][_id];
if (fromBalance < _amount) {
    revert ERC6909InsufficientBalance(_from, fromBalance, _amount, _id);
}
unchecked {
    s.balanceOf[_from][_id] = fromBalance - _amount;
}

similar to the transferFrom of the ERC6909TransferFacet

Copy link
Collaborator

@maxnorm maxnorm Mar 11, 2026

Choose a reason for hiding this comment

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

@beebozy Please review this too. What do you think of making the balance check and update after the allowance check and update?

Copy link
Collaborator

@maxnorm maxnorm Mar 12, 2026

Choose a reason for hiding this comment

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

After reviewing a bit more, i would do something like this:

function burnFrom(address _from, uint256 _id, uint256 _amount) {
    if (_from == address(0)) {
        revert ERC6909InvalidSender(_from);
    }

    ERC6909Storage storage s = getStorage();

    uint256 currentAllowance = s.allowance[_from][msg.sender][_id];
    
    if (msg.sender != _from && currentAllowance < type(uint256).max) {
        if (currentAllowance < _amount) {
            revert ERC6909InsufficientAllowance(msg.sender, currentAllowance, _amount, _id);
        }
        unchecked {
            s.allowance[_from][msg.sender][_id] = currentAllowance - _amount;
        }
    }

    uint256 fromBalance = s.balanceOf[_from][_id];
    if (fromBalance < _amount) {
        revert ERC6909InsufficientBalance(_from, fromBalance, _amount, _id);
    }
    unchecked {
        s.balanceOf[_from][_id] = fromBalance - _amount;
    }

    emit Transfer(msg.sender, _from, address(0), _id, _amount);
}

Current issues in the code:

  • Only msg.sender with allowance can burn (no msg.sender is _from or operator)
  • No check if _from have a sufficient balance to burn the amount
  • we always emit the Transfer event even if the burn isn't done

Question that need reflexion: do we allow an operator to burn tokens? (in the code proposed above, i just allowed the msg.sender to the the _from)

@beebozy
Copy link
Contributor Author

beebozy commented Mar 11, 2026

Fixed @maxnorm kindly review sir..I have a question, declaring the storage before the normal check does it introduce a bug, or it does not follow the normal security pattern.. I was thinking the check and the storage are different things.

}

uint256 fromBalance = s.balanceOf[_from][_id];

Copy link
Collaborator

@maxnorm maxnorm Mar 11, 2026

Choose a reason for hiding this comment

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

@beebozy Please review this too. What do you think of making the balance check and update after the allowance check and update?

@maxnorm
Copy link
Collaborator

maxnorm commented Mar 11, 2026

Fixed @maxnorm kindly review sir..I have a question, declaring the storage before the normal check does it introduce a bug, or it does not follow the normal security pattern.. I was thinking the check and the storage are different things.

Hey @beebozy, you are right these a 2 different things. I'm mostly in order to fail fast and avoid unnecessary code execution if the check is true.

Practically, both ways would work as expected without security issues.

@beebozy
Copy link
Contributor Author

beebozy commented Mar 12, 2026

@maxnorm fixed..So sorry for the back and fort.. Kindly review

}

uint256 fromBalance = s.balanceOf[_from][_id];

Copy link
Collaborator

@maxnorm maxnorm Mar 12, 2026

Choose a reason for hiding this comment

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

After reviewing a bit more, i would do something like this:

function burnFrom(address _from, uint256 _id, uint256 _amount) {
    if (_from == address(0)) {
        revert ERC6909InvalidSender(_from);
    }

    ERC6909Storage storage s = getStorage();

    uint256 currentAllowance = s.allowance[_from][msg.sender][_id];
    
    if (msg.sender != _from && currentAllowance < type(uint256).max) {
        if (currentAllowance < _amount) {
            revert ERC6909InsufficientAllowance(msg.sender, currentAllowance, _amount, _id);
        }
        unchecked {
            s.allowance[_from][msg.sender][_id] = currentAllowance - _amount;
        }
    }

    uint256 fromBalance = s.balanceOf[_from][_id];
    if (fromBalance < _amount) {
        revert ERC6909InsufficientBalance(_from, fromBalance, _amount, _id);
    }
    unchecked {
        s.balanceOf[_from][_id] = fromBalance - _amount;
    }

    emit Transfer(msg.sender, _from, address(0), _id, _amount);
}

Current issues in the code:

  • Only msg.sender with allowance can burn (no msg.sender is _from or operator)
  • No check if _from have a sufficient balance to burn the amount
  • we always emit the Transfer event even if the burn isn't done

Question that need reflexion: do we allow an operator to burn tokens? (in the code proposed above, i just allowed the msg.sender to the the _from)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Refactor ERC6909 Implementation

2 participants