Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 111 additions & 15 deletions src/ERC20/ERC20/ERC20.sol
Original file line number Diff line number Diff line change
@@ -1,75 +1,155 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.30;


/**
* @title ERC20
* @notice A minimal ERC-20 token implementation using ERC-8042 diamond storage.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not a minimal implementation of ERC20. It is a full implementation of the ERC20 standard. It just looks minimal because other implementations are bloated and spaghetti code.

* @dev This contract avoids Solidity inheritance, constructors, and modifiers as per Compose conventions.
* It includes ERC-6093 custom errors for efficient error handling.
*/
contract ERC20 {

// ERC-6093: Custom errors for ERC-20
/// @notice Thrown when an account has insufficient balance for a transfer or burn.
/// @param _sender Address attempting the transfer.
/// @param _balance Current balance of the sender.
/// @param _needed Amount required to complete the operation.
error ERC20InsufficientBalance(address _sender, uint256 _balance, uint256 _needed);

/// @notice Thrown when the sender address is invalid (e.g., zero address).
/// @param _sender Invalid sender address.
error ERC20InvalidSender(address _sender);

/// @notice Thrown when the receiver address is invalid (e.g., zero address).
/// @param _receiver Invalid receiver address.
error ERC20InvalidReceiver(address _receiver);

/// @notice Thrown when a spender tries to use more than the approved allowance.
/// @param _spender Address attempting to spend.
/// @param _allowance Current allowance for the spender.
/// @param _needed Amount required to complete the operation.
error ERC20InsufficientAllowance(address _spender, uint256 _allowance, uint256 _needed);

/// @notice Thrown when the approver address is invalid (e.g., zero address).
/// @param _approver Invalid approver address.
error ERC20InvalidApprover(address _approver);

/// @notice Thrown when the spender address is invalid (e.g., zero address).
/// @param _spender Invalid spender address.
error ERC20InvalidSpender(address _spender);



/// @notice Emitted when an approval is made for a spender by an owner.
/// @param _owner The address granting the allowance.
/// @param _spender The address receiving the allowance.
/// @param _value The amount approved.
event Approval(address indexed _owner, address indexed _spender, uint256 _value);

/// @notice Emitted when tokens are transferred between two addresses.
/// @param _from Address sending the tokens.
/// @param _to Address receiving the tokens.
/// @param _value Amount of tokens transferred.
event Transfer(address indexed _from, address indexed _to, uint256 _value);

// Struct storage position defined by keccak256 hash
// of diamond storage identifier
/// @dev Storage position determined by the keccak256 hash of the diamond storage identifier.
bytes32 constant STORAGE_POSITION = keccak256("compose.erc20");

// Storage defined using the ERC-8042 standard
// @custom:storage-location erc8042:compose.erc20
/**
* @dev ERC-8042 compliant storage struct for ERC20 token data.
* @custom:storage-location erc8042:compose.erc20
*/
struct ERC20Storage {
string name;
string symbol;
uint8 decimals;
uint256 totalSupply;
mapping(address owner => uint256 balance) balanceOf;
mapping(address owner => mapping(address spender => uint256 allowance)) allowances;
mapping(address owner => mapping(address spender => uint256 allowance)) allowances;
}

/**
* @notice Returns the ERC20 storage struct from the predefined diamond storage slot.
* @dev Uses inline assembly to set the storage slot reference.
* @return s The ERC20 storage struct reference.
*/
function getStorage() internal pure returns (ERC20Storage storage s) {
bytes32 position = STORAGE_POSITION;
assembly {
s.slot := position
}
}


/**
* @notice Returns the name of the token.
* @return The token name.
*/
function name() external view returns (string memory) {
return getStorage().name;
}

/**
* @notice Returns the symbol of the token.
* @return The token symbol.
*/
function symbol() external view returns (string memory) {
return getStorage().symbol;
}

/**
* @notice Returns the number of decimals used for token precision.
* @return The number of decimals.
*/
function decimals() external view returns (uint8) {
return getStorage().decimals;
}

/**
* @notice Returns the total supply of tokens.
* @return The total token supply.
*/
function totalSupply() external view returns (uint256) {
return getStorage().totalSupply;
}

/**
* @notice Returns the balance of a specific account.
* @param _account The address of the account.
* @return The account balance.
*/
function balanceOf(address _account) external view returns (uint256) {
return getStorage().balanceOf[_account];
}

/**
* @notice Returns the remaining number of tokens that a spender is allowed to spend on behalf of an owner.
* @param _owner The address of the token owner.
* @param _spender The address of the spender.
* @return The remaining allowance.
*/
function allowance(address _owner, address _spender) external view returns (uint256) {
return getStorage().allowances[_owner][_spender];
}

/**
* @notice Approves a spender to transfer up to a certain amount of tokens on behalf of the caller.
* @dev Emits an {Approval} event.
* @param _spender The address approved to spend tokens.
* @param _value The number of tokens to approve.
*/
function approve(address _spender, uint256 _value) external {
ERC20Storage storage s = getStorage();
if (_spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
s.allowances[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
emit Approval(msg.sender, _spender, _value);
}

/**
* @notice Transfers tokens to another address.
* @dev Emits a {Transfer} event.
* @param _to The address to receive the tokens.
* @param _value The amount of tokens to transfer.
*/
function transfer(address _to, uint256 _value) external {
ERC20Storage storage s = getStorage();
if (_to == address(0)) {
Expand All @@ -86,6 +166,13 @@ contract ERC20 {
emit Transfer(msg.sender, _to, _value);
}

/**
* @notice Transfers tokens on behalf of another account, provided sufficient allowance exists.
* @dev Emits a {Transfer} event and decreases the spender's allowance.
* @param _from The address to transfer tokens from.
* @param _to The address to transfer tokens to.
* @param _value The amount of tokens to transfer.
*/
function transferFrom(address _from, address _to, uint256 _value) external {
ERC20Storage storage s = getStorage();
if (_from == address(0)) {
Expand All @@ -108,8 +195,13 @@ contract ERC20 {
s.balanceOf[_to] += _value;
}
emit Transfer(_from, _to, _value);
}

}

/**
* @notice Burns (destroys) a specific amount of tokens from the caller's balance.
* @dev Emits a {Transfer} event to the zero address.
* @param _value The amount of tokens to burn.
*/
function burn(uint256 _value) external {
ERC20Storage storage s = getStorage();
uint256 balance = s.balanceOf[msg.sender];
Expand All @@ -122,6 +214,12 @@ contract ERC20 {
emit Transfer(msg.sender, address(0), _value);
}

/**
* @notice Burns tokens from another account, deducting from the caller's allowance.
* @dev Emits a {Transfer} event to the zero address.
* @param _account The address whose tokens will be burned.
* @param _value The amount of tokens to burn.
*/
function burnFrom(address _account, uint256 _value) external {
ERC20Storage storage s = getStorage();
uint256 currentAllowance = s.allowances[_account][msg.sender];
Expand All @@ -135,9 +233,7 @@ contract ERC20 {
unchecked {
s.allowances[_account][msg.sender] = currentAllowance - _value;
s.balanceOf[_account] = balance - _value;
}
}
emit Transfer(msg.sender, address(0), _value);
}


}