From 5007a2a08140780d59bc4fca7e8ade00faef2aa3 Mon Sep 17 00:00:00 2001 From: beebozy Date: Thu, 30 Oct 2025 17:51:38 -0700 Subject: [PATCH 1/8] Implemented facet to move ERC20 tokens accross network --- src/ERC7802/ERC7802Facet.sol | 150 ++++++++++++++++++++++++ src/ERC7802/libraries/LibERC7802.sol | 163 +++++++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 src/ERC7802/ERC7802Facet.sol create mode 100644 src/ERC7802/libraries/LibERC7802.sol diff --git a/src/ERC7802/ERC7802Facet.sol b/src/ERC7802/ERC7802Facet.sol new file mode 100644 index 00000000..680222eb --- /dev/null +++ b/src/ERC7802/ERC7802Facet.sol @@ -0,0 +1,150 @@ + // SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +contract ERC7802Facet { + /// @notice Revert when a provided receiver is invalid(e.g,zero address) . + /// @param _receiver The invalid reciever address. + error ERC7802InvalidReciever(address _receiver); + + /// @notice Thrown when the sender address is invalid (e.g., zero address). + /// @param _sender The invalid sender address. + + error ERC7802InvalidSender(address _sender); + + /// @notice Revert when caller is not a trusted bridge. + /// @param _caller The unauthorized caller. + error ERC7802InvalidBridgeAccount(address _caller); + + // @notice Revert when caller address is invalid. + /// @param _caller is the invalid address. + error ERC7802InvalidCallerAddress(address _caller); + + error ERC7802InvalidOwner(address bridge); + + // @notice Revert when an interface id is not supported. // the interfaces supported are IERC165 and 7805 + /// @param interfaceId The unsupported interface id. + error ERC7802InvalidInterfaceId(bytes4 interfaceId); + + +error ERC7802InsufficientBalance(address from,uint256 accountBalance,uint256 value); + /// @notice Emitted when tokens are minted via a cross-chain bridge. + /// @param to The recipient of minted tokens. + /// @param amount The amount minted. + /// @param sender The bridge account that triggered the mint (msg.sender). + + event CrosschainMint(address indexed to, uint256 amount, address indexed sender); + + /// @notice Emitted when a crosschain transfer burns tokens. + /// @param from Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + /// @param sender Address of the caller (msg.sender) who invoked crosschainBurn. + event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); + + /// @notice Mint tokens through a crosschain transfer. + /// @param to Address to mint tokens to. + /// @param amount Amount of tokens to mint. + + event Transfer(address indexed from, address indexed to, uint256 amount); + + /// @notice Emitted when an interface ID is registered or supported. +/// @param interfaceId The supported interface identifier. +event Interface(bytes4 indexed interfaceId); + + /// @notice Storage slot for ERC-7802 using ERC8042 + + bytes32 constant STORAGE_POSITION = keccak256("compose.erc7802"); + + struct ERC7802Storage { + address owner; + uint256 totalSupply; + mapping(address owner => uint256 balance) balanceOf; + mapping(address => bool) _trustedBridges; + mapping(bytes4 => bool) _supportedInterfaces; + } + + /// @notice Return storage pointer to ERC7802Storage + /// @dev Inline assembly to point storage to a fixed slot. + /// @return s Pointer to storage struct. + + function getStorage() internal pure returns (ERC7802Storage storage s) { + bytes32 position = STORAGE_POSITION; + + assembly { + s.slot := position + } + } + + function getOwner() external view returns (address) { + return getStorage().owner; + } + + + /// @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 Internal crosschain mint logic. MUST be called only after validating caller. + /// @dev Increases totalSupply and recipient balance and emits CrosschainMint. + /// @param _account The account to mint tokens to. + /// @param _value The amount to mint. + function crooschainMint(address _account, uint256 _value) external { + ERC7802Storage storage s = getStorage(); + + if (s._trustedBridges[msg.sender] == false) revert ERC7802InvalidBridgeAccount(msg.sender); + if (_account == address(0)) revert ERC7802InvalidReciever(address(0)); + + unchecked { + s.totalSupply += _value; + s.balanceOf[_account] += _value; + } + + emit CrosschainMint(_account, _value, msg.sender); + } + + /// @notice Internal crosschain burn logic. MUST be called only after validating caller. + /// @dev Decreases totalSupply and the `from` balance and emits CrosschainBurn. + /// @param _from The account to burn tokens from. + /// @param _value The amount to burn. + function crosschainBurn(address _from, uint256 _value) external { + ERC7802Storage storage s = getStorage(); + + if (s._trustedBridges[msg.sender] == false) revert ERC7802InvalidBridgeAccount(msg.sender); + if (_from == address(0)) revert ERC7802InvalidReciever(address(0)); + + uint256 accountBalance = s.balanceOf[_from]; + + if (accountBalance < _value) revert ERC7802InsufficientBalance(_from, accountBalance, _value); + + unchecked { + s.totalSupply -= _value; + s.balanceOf[_from] -= _value; + } + + emit CrosschainBurn(_from, _value, msg.sender); + } + + //ERC165 and 7805 supports + /// @notice Query whether an interface id is supported (ERC-165 style). + /// @param interfaceId The interface id to query. + + function supportInterface(bytes4 interfaceId) external { + ERC7802Storage storage s = getStorage(); + + if (interfaceId != 0x33331994 || interfaceId == 0x01ffc9a7) revert ERC7802InvalidInterfaceId(interfaceId); + + s._supportedInterfaces[interfaceId] = true; + + emit Interface(interfaceId); + } +} diff --git a/src/ERC7802/libraries/LibERC7802.sol b/src/ERC7802/libraries/LibERC7802.sol new file mode 100644 index 00000000..2a39711e --- /dev/null +++ b/src/ERC7802/libraries/LibERC7802.sol @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/// @title LibERC20 — ERC-7802 Library +/// @notice Provides internal functions and storage layout for ERC-7802 token logic. +/// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions +library LibERC7802 { + /// @notice Revert when a provided receiver is invalid(e.g,zero address) . + /// @param _receiver The invalid reciever address. + error ERC7802InvalidReciever(address _receiver); + + /// @notice Thrown when the sender address is invalid (e.g., zero address). + /// @param _sender The invalid sender address. + + error ERC7802InvalidSender(address _sender); + + /// @notice Revert when caller is not a trusted bridge. + /// @param _caller The unauthorized caller. + error ERC7802InvalidBridgeAccount(address _caller); + + // @notice Revert when caller address is invalid. + /// @param _caller is the invalid address. + error ERC7802InvalidCallerAddress(address _caller); + + error ERC7802InvalidOwner(address bridge); + + // @notice Revert when an interface id is not supported. // the interfaces supported are IERC165 and 7805 + /// @param interfaceId The unsupported interface id. + error ERC7802InvalidInterfaceId(bytes4 interfaceId); + + + +error ERC7802InsufficientBalance(address from,uint256 accountBalance,uint256 value); + /// @notice Emitted when tokens are minted via a cross-chain bridge. + /// @param to The recipient of minted tokens. + /// @param amount The amount minted. + /// @param sender The bridge account that triggered the mint (msg.sender). + + event CrosschainMint(address indexed to, uint256 amount, address indexed sender); + + /// @notice Emitted when a crosschain transfer burns tokens. + /// @param from Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + /// @param sender Address of the caller (msg.sender) who invoked crosschainBurn. + event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); + + /// @notice Mint tokens through a crosschain transfer. + /// @param to Address to mint tokens to. + /// @param amount Amount of tokens to mint. + + event Transfer(address indexed from, address indexed to, uint256 amount); + + /// @notice Emitted when there is a valid supported interface call + ///@param InterfaceId The supported interface identifier. + event Interface(bytes4 indexed InterfaceId); + + /// @notice Storage slot for ERC-7802 using ERC8042 for storage location standardization + + bytes32 constant STORAGE_POSITION = keccak256("compose.erc7802"); + + struct ERC7802Storage { + address owner; + uint256 totalSupply; + mapping(address owner => uint256 balance) balanceOf; + mapping(address => bool) _trustedBridges; + mapping(bytes4 => bool) _supportedInterfaces; + } + + /// @notice Return storage pointer to ERC7802Storage + /// @dev Inline assembly to point storage to a fixed slot. + /// @return s Pointer to storage struct. + + function getStorage() internal pure returns (ERC7802Storage storage s) { + bytes32 position = STORAGE_POSITION; + + assembly { + s.slot := position + } + } + + // the bridge account must be a trusted account + /// @notice Internal crosschain mint logic. MUST be called only after validating caller. + /// @dev Increases totalSupply and recipient balance and emits CrosschainMint. + /// @param _account The account to mint tokens to. + /// @param _value The amount to mint. + function crooschainMint(address _account, uint256 _value) internal { + ERC7802Storage storage s = getStorage(); + + if (s._trustedBridges[msg.sender] == false) revert ERC7802InvalidBridgeAccount(msg.sender); + if (_account == address(0)) revert ERC7802InvalidReciever(address(0)); + + unchecked { + s.totalSupply += _value; + s.balanceOf[_account] += _value; + } + + emit CrosschainMint(_account, _value, msg.sender); + } + + /// @notice Internal crosschain burn logic. MUST be called only after validating caller. + /// @dev Decreases totalSupply and the `from` balance and emits CrosschainBurn. + /// @param _from The account to burn tokens from. + /// @param _value The amount to burn. + function crosschainBurn(address _from, uint256 _value) internal { + ERC7802Storage storage s = getStorage(); + + if (s._trustedBridges[msg.sender] == false) revert ERC7802InvalidBridgeAccount(msg.sender); + if (_from == address(0)) revert ERC7802InvalidReciever(address(0)); + + uint256 accountBalance = s.balanceOf[_from]; + + if (accountBalance < _value) revert ERC7802InsufficientBalance(_from,accountBalance, _value); + + unchecked { + s.totalSupply -= _value; + s.balanceOf[_from] -= _value; + } + + emit CrosschainBurn(_from, _value, msg.sender); + } + + //ERC165 and 7805 supports + /// @notice Query whether an interface id is supported (ERC-165 style). + /// @param interfaceId The interface id to query. + + function supportInterface(bytes4 interfaceId) internal { + ERC7802Storage storage s = getStorage(); + if (interfaceId != 0x33331994 || interfaceId == 0x01ffc9a7) revert ERC7802InvalidInterfaceId(interfaceId); + + s._supportedInterfaces[interfaceId] = true; + + emit Interface(interfaceId); + } + + // @notice Add a trusted bridge address. Owner-only. + /// @param _bridge The bridge address to add. + function addTrustedBridges(address _bridge) internal { + ERC7802Storage storage s = getStorage(); + + if (msg.sender != s.owner) revert ERC7802InvalidOwner(msg.sender); + s._trustedBridges[_bridge] = true; + } + + /// @notice Remove a trusted bridge address. Owner-only. + /// @param _bridge The bridge address to remove. + function removeTrustedBridges(address _bridge) internal { + ERC7802Storage storage s = getStorage(); + + if (msg.sender != s.owner) revert ERC7802InvalidOwner(msg.sender); + s._trustedBridges[_bridge] = false; + } + + /// @notice Internal check to check if the bridge is trusted. + /// @dev Reverts if caller is zero or not in the trusted bridges mapping. + /// @param _caller The address to validate + + function _checkTokenBridge(address _caller) internal { + ERC7802Storage storage s = getStorage(); + + if (_caller == address(0)) revert ERC7802InvalidBridgeAccount(address(0)); + if (s._trustedBridges[_caller] == false) revert ERC7802InvalidBridgeAccount(_caller); + } +} From b22ffcbd638e91783b92ec0cd371fc84b0168068 Mon Sep 17 00:00:00 2001 From: beebozy Date: Thu, 30 Oct 2025 17:53:01 -0700 Subject: [PATCH 2/8] Implemented facet to move ERC20 tokens accross network --- src/ERC7802/ERC7802Facet.sol | 20 +++++++++----------- src/ERC7802/libraries/LibERC7802.sol | 12 +++++------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/ERC7802/ERC7802Facet.sol b/src/ERC7802/ERC7802Facet.sol index 680222eb..19139135 100644 --- a/src/ERC7802/ERC7802Facet.sol +++ b/src/ERC7802/ERC7802Facet.sol @@ -25,8 +25,7 @@ contract ERC7802Facet { /// @param interfaceId The unsupported interface id. error ERC7802InvalidInterfaceId(bytes4 interfaceId); - -error ERC7802InsufficientBalance(address from,uint256 accountBalance,uint256 value); + error ERC7802InsufficientBalance(address from, uint256 accountBalance, uint256 value); /// @notice Emitted when tokens are minted via a cross-chain bridge. /// @param to The recipient of minted tokens. /// @param amount The amount minted. @@ -46,9 +45,9 @@ error ERC7802InsufficientBalance(address from,uint256 accountBalance,uint256 val event Transfer(address indexed from, address indexed to, uint256 amount); - /// @notice Emitted when an interface ID is registered or supported. -/// @param interfaceId The supported interface identifier. -event Interface(bytes4 indexed interfaceId); + /// @notice Emitted when an interface ID is registered or supported. + /// @param interfaceId The supported interface identifier. + event Interface(bytes4 indexed interfaceId); /// @notice Storage slot for ERC-7802 using ERC8042 @@ -78,10 +77,9 @@ event Interface(bytes4 indexed interfaceId); return getStorage().owner; } - - /// @notice Returns the total supply of tokens. - /// @return The total token supply. - + /// @notice Returns the total supply of tokens. + /// @return The total token supply. + function totalSupply() external view returns (uint256) { return getStorage().totalSupply; } @@ -117,7 +115,7 @@ event Interface(bytes4 indexed interfaceId); /// @param _from The account to burn tokens from. /// @param _value The amount to burn. function crosschainBurn(address _from, uint256 _value) external { - ERC7802Storage storage s = getStorage(); + ERC7802Storage storage s = getStorage(); if (s._trustedBridges[msg.sender] == false) revert ERC7802InvalidBridgeAccount(msg.sender); if (_from == address(0)) revert ERC7802InvalidReciever(address(0)); @@ -137,7 +135,7 @@ event Interface(bytes4 indexed interfaceId); //ERC165 and 7805 supports /// @notice Query whether an interface id is supported (ERC-165 style). /// @param interfaceId The interface id to query. - + function supportInterface(bytes4 interfaceId) external { ERC7802Storage storage s = getStorage(); diff --git a/src/ERC7802/libraries/LibERC7802.sol b/src/ERC7802/libraries/LibERC7802.sol index 2a39711e..7c73f06e 100644 --- a/src/ERC7802/libraries/LibERC7802.sol +++ b/src/ERC7802/libraries/LibERC7802.sol @@ -28,9 +28,7 @@ library LibERC7802 { /// @param interfaceId The unsupported interface id. error ERC7802InvalidInterfaceId(bytes4 interfaceId); - - -error ERC7802InsufficientBalance(address from,uint256 accountBalance,uint256 value); + error ERC7802InsufficientBalance(address from, uint256 accountBalance, uint256 value); /// @notice Emitted when tokens are minted via a cross-chain bridge. /// @param to The recipient of minted tokens. /// @param amount The amount minted. @@ -44,7 +42,7 @@ error ERC7802InsufficientBalance(address from,uint256 accountBalance,uint256 val /// @param sender Address of the caller (msg.sender) who invoked crosschainBurn. event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); - /// @notice Mint tokens through a crosschain transfer. + /// @notice Mint tokens through a crosschain transfer. /// @param to Address to mint tokens to. /// @param amount Amount of tokens to mint. @@ -109,7 +107,7 @@ error ERC7802InsufficientBalance(address from,uint256 accountBalance,uint256 val uint256 accountBalance = s.balanceOf[_from]; - if (accountBalance < _value) revert ERC7802InsufficientBalance(_from,accountBalance, _value); + if (accountBalance < _value) revert ERC7802InsufficientBalance(_from, accountBalance, _value); unchecked { s.totalSupply -= _value; @@ -122,9 +120,9 @@ error ERC7802InsufficientBalance(address from,uint256 accountBalance,uint256 val //ERC165 and 7805 supports /// @notice Query whether an interface id is supported (ERC-165 style). /// @param interfaceId The interface id to query. - + function supportInterface(bytes4 interfaceId) internal { - ERC7802Storage storage s = getStorage(); + ERC7802Storage storage s = getStorage(); if (interfaceId != 0x33331994 || interfaceId == 0x01ffc9a7) revert ERC7802InvalidInterfaceId(interfaceId); s._supportedInterfaces[interfaceId] = true; From aeee4aee1fe01cf9f56d072ce47ed18f5e519f54 Mon Sep 17 00:00:00 2001 From: beebozy Date: Sun, 16 Nov 2025 13:42:42 -0800 Subject: [PATCH 3/8] changes on ERC20 crosschain facet and library --- ...7802Facet.sol => ERC20BridgeableFacet.sol} | 62 ++++++--------- src/ERC7802/libraries/LibERC7802.sol | 78 ++++++++----------- 2 files changed, 58 insertions(+), 82 deletions(-) rename src/ERC7802/{ERC7802Facet.sol => ERC20BridgeableFacet.sol} (64%) diff --git a/src/ERC7802/ERC7802Facet.sol b/src/ERC7802/ERC20BridgeableFacet.sol similarity index 64% rename from src/ERC7802/ERC7802Facet.sol rename to src/ERC7802/ERC20BridgeableFacet.sol index 19139135..254defb2 100644 --- a/src/ERC7802/ERC7802Facet.sol +++ b/src/ERC7802/ERC20BridgeableFacet.sol @@ -1,71 +1,71 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; -contract ERC7802Facet { +contract ERC20BridgeableFacet { /// @notice Revert when a provided receiver is invalid(e.g,zero address) . /// @param _receiver The invalid reciever address. - error ERC7802InvalidReciever(address _receiver); + error ERC20InvalidReciever(address _receiver); /// @notice Thrown when the sender address is invalid (e.g., zero address). /// @param _sender The invalid sender address. - error ERC7802InvalidSender(address _sender); + error ERC20InvalidSender(address _sender); /// @notice Revert when caller is not a trusted bridge. /// @param _caller The unauthorized caller. - error ERC7802InvalidBridgeAccount(address _caller); + error ERC20InvalidBridgeAccount(address _caller); // @notice Revert when caller address is invalid. /// @param _caller is the invalid address. - error ERC7802InvalidCallerAddress(address _caller); + error ERC20InvalidCallerAddress(address _caller); - error ERC7802InvalidOwner(address bridge); + error ERC20InvalidOwner(address _bridge); // @notice Revert when an interface id is not supported. // the interfaces supported are IERC165 and 7805 /// @param interfaceId The unsupported interface id. - error ERC7802InvalidInterfaceId(bytes4 interfaceId); + error ERC20InvalidInterfaceId(bytes4 _interfaceId); - error ERC7802InsufficientBalance(address from, uint256 accountBalance, uint256 value); + error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); /// @notice Emitted when tokens are minted via a cross-chain bridge. /// @param to The recipient of minted tokens. /// @param amount The amount minted. /// @param sender The bridge account that triggered the mint (msg.sender). - event CrosschainMint(address indexed to, uint256 amount, address indexed sender); + event CrosschainMint(address indexed _to, uint256 _amount, address indexed _sender); /// @notice Emitted when a crosschain transfer burns tokens. /// @param from Address of the account tokens are being burned from. /// @param amount Amount of tokens burned. /// @param sender Address of the caller (msg.sender) who invoked crosschainBurn. - event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); + event CrosschainBurn(address indexed _from, uint256 _amount, address indexed _sender); /// @notice Mint tokens through a crosschain transfer. /// @param to Address to mint tokens to. /// @param amount Amount of tokens to mint. - event Transfer(address indexed from, address indexed to, uint256 amount); + event Transfer(address indexed _from, address indexed _to, uint256 _amount); /// @notice Emitted when an interface ID is registered or supported. /// @param interfaceId The supported interface identifier. - event Interface(bytes4 indexed interfaceId); + event Interface(bytes4 indexed _interfaceId); /// @notice Storage slot for ERC-7802 using ERC8042 bytes32 constant STORAGE_POSITION = keccak256("compose.erc7802"); - struct ERC7802Storage { + struct ERC20BridgeableStorage { address owner; uint256 totalSupply; mapping(address owner => uint256 balance) balanceOf; - mapping(address => bool) _trustedBridges; - mapping(bytes4 => bool) _supportedInterfaces; + mapping(address => bool) trustedBridges; + mapping(bytes4 => bool) supportedInterfaces; } /// @notice Return storage pointer to ERC7802Storage /// @dev Inline assembly to point storage to a fixed slot. /// @return s Pointer to storage struct. - function getStorage() internal pure returns (ERC7802Storage storage s) { + function getStorage() internal pure returns (ERC20BridgeableStorage storage s) { bytes32 position = STORAGE_POSITION; assembly { @@ -96,11 +96,11 @@ contract ERC7802Facet { /// @dev Increases totalSupply and recipient balance and emits CrosschainMint. /// @param _account The account to mint tokens to. /// @param _value The amount to mint. - function crooschainMint(address _account, uint256 _value) external { - ERC7802Storage storage s = getStorage(); + function crosschainMint(address _account, uint256 _value) external { + ERC20BridgeableStorage storage s = getStorage(); - if (s._trustedBridges[msg.sender] == false) revert ERC7802InvalidBridgeAccount(msg.sender); - if (_account == address(0)) revert ERC7802InvalidReciever(address(0)); + if (s.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); + if (_account == address(0)) revert ERC20InvalidReciever(address(0)); unchecked { s.totalSupply += _value; @@ -115,14 +115,14 @@ contract ERC7802Facet { /// @param _from The account to burn tokens from. /// @param _value The amount to burn. function crosschainBurn(address _from, uint256 _value) external { - ERC7802Storage storage s = getStorage(); + EERC20BridgeableStorage storage s = getStorage(); - if (s._trustedBridges[msg.sender] == false) revert ERC7802InvalidBridgeAccount(msg.sender); - if (_from == address(0)) revert ERC7802InvalidReciever(address(0)); + if (s.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); + if (_from == address(0)) revert ERC20InvalidReciever(address(0)); uint256 accountBalance = s.balanceOf[_from]; - if (accountBalance < _value) revert ERC7802InsufficientBalance(_from, accountBalance, _value); + if (accountBalance < _value) revert ERC20InsufficientBalance(_from, accountBalance, _value); unchecked { s.totalSupply -= _value; @@ -132,17 +132,5 @@ contract ERC7802Facet { emit CrosschainBurn(_from, _value, msg.sender); } - //ERC165 and 7805 supports - /// @notice Query whether an interface id is supported (ERC-165 style). - /// @param interfaceId The interface id to query. - - function supportInterface(bytes4 interfaceId) external { - ERC7802Storage storage s = getStorage(); - - if (interfaceId != 0x33331994 || interfaceId == 0x01ffc9a7) revert ERC7802InvalidInterfaceId(interfaceId); - - s._supportedInterfaces[interfaceId] = true; - - emit Interface(interfaceId); - } + } diff --git a/src/ERC7802/libraries/LibERC7802.sol b/src/ERC7802/libraries/LibERC7802.sol index 7c73f06e..7a8a51d1 100644 --- a/src/ERC7802/libraries/LibERC7802.sol +++ b/src/ERC7802/libraries/LibERC7802.sol @@ -4,71 +4,71 @@ pragma solidity >=0.8.30; /// @title LibERC20 — ERC-7802 Library /// @notice Provides internal functions and storage layout for ERC-7802 token logic. /// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions -library LibERC7802 { +library LibERC20Bridgeable { /// @notice Revert when a provided receiver is invalid(e.g,zero address) . /// @param _receiver The invalid reciever address. - error ERC7802InvalidReciever(address _receiver); + error ERC20InvalidReciever(address _receiver); /// @notice Thrown when the sender address is invalid (e.g., zero address). /// @param _sender The invalid sender address. - error ERC7802InvalidSender(address _sender); + error ERC20InvalidSender(address _sender); /// @notice Revert when caller is not a trusted bridge. /// @param _caller The unauthorized caller. - error ERC7802InvalidBridgeAccount(address _caller); + error ERC20InvalidBridgeAccount(address _caller); // @notice Revert when caller address is invalid. /// @param _caller is the invalid address. - error ERC7802InvalidCallerAddress(address _caller); + error ERC20InvalidCallerAddress(address _caller); - error ERC7802InvalidOwner(address bridge); + error ERC20InvalidOwner(address _bridge); // @notice Revert when an interface id is not supported. // the interfaces supported are IERC165 and 7805 /// @param interfaceId The unsupported interface id. - error ERC7802InvalidInterfaceId(bytes4 interfaceId); + error ERC20InvalidInterfaceId(bytes4 _interfaceId); - error ERC7802InsufficientBalance(address from, uint256 accountBalance, uint256 value); + error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); /// @notice Emitted when tokens are minted via a cross-chain bridge. /// @param to The recipient of minted tokens. /// @param amount The amount minted. /// @param sender The bridge account that triggered the mint (msg.sender). - event CrosschainMint(address indexed to, uint256 amount, address indexed sender); + event CrosschainMint(address indexed _to, uint256 _amount, address indexed _sender); /// @notice Emitted when a crosschain transfer burns tokens. /// @param from Address of the account tokens are being burned from. /// @param amount Amount of tokens burned. /// @param sender Address of the caller (msg.sender) who invoked crosschainBurn. - event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); + event CrosschainBurn(address indexed _from, uint256 _amount, address indexed _sender); /// @notice Mint tokens through a crosschain transfer. /// @param to Address to mint tokens to. /// @param amount Amount of tokens to mint. - event Transfer(address indexed from, address indexed to, uint256 amount); + event Transfer(address indexed _from, address indexed _to, uint256 _amount); /// @notice Emitted when there is a valid supported interface call ///@param InterfaceId The supported interface identifier. - event Interface(bytes4 indexed InterfaceId); + event Interface(bytes4 indexed _InterfaceId); /// @notice Storage slot for ERC-7802 using ERC8042 for storage location standardization bytes32 constant STORAGE_POSITION = keccak256("compose.erc7802"); - struct ERC7802Storage { + struct ERC20BridgeableStorage { address owner; uint256 totalSupply; mapping(address owner => uint256 balance) balanceOf; - mapping(address => bool) _trustedBridges; - mapping(bytes4 => bool) _supportedInterfaces; + mapping(address => bool) trustedBridges; + mapping(bytes4 => bool) supportedInterfaces; } /// @notice Return storage pointer to ERC7802Storage /// @dev Inline assembly to point storage to a fixed slot. /// @return s Pointer to storage struct. - function getStorage() internal pure returns (ERC7802Storage storage s) { + function getStorage() internal pure returns (ERC20BridgeableStorage storage s) { bytes32 position = STORAGE_POSITION; assembly { @@ -82,10 +82,10 @@ library LibERC7802 { /// @param _account The account to mint tokens to. /// @param _value The amount to mint. function crooschainMint(address _account, uint256 _value) internal { - ERC7802Storage storage s = getStorage(); + ERC20BridgeableStorage storage s = getStorage(); - if (s._trustedBridges[msg.sender] == false) revert ERC7802InvalidBridgeAccount(msg.sender); - if (_account == address(0)) revert ERC7802InvalidReciever(address(0)); + if (s._trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); + if (_account == address(0)) revert ERC20InvalidReciever(address(0)); unchecked { s.totalSupply += _value; @@ -100,14 +100,14 @@ library LibERC7802 { /// @param _from The account to burn tokens from. /// @param _value The amount to burn. function crosschainBurn(address _from, uint256 _value) internal { - ERC7802Storage storage s = getStorage(); + ERC20BridgeableStorage s = getStorage(); - if (s._trustedBridges[msg.sender] == false) revert ERC7802InvalidBridgeAccount(msg.sender); - if (_from == address(0)) revert ERC7802InvalidReciever(address(0)); + if (s._trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); + if (_from == address(0)) revert ERC20InvalidReciever(address(0)); uint256 accountBalance = s.balanceOf[_from]; - if (accountBalance < _value) revert ERC7802InsufficientBalance(_from, accountBalance, _value); + if (accountBalance < _value) revert ERC20InsufficientBalance(_from, accountBalance, _value); unchecked { s.totalSupply -= _value; @@ -117,35 +117,23 @@ library LibERC7802 { emit CrosschainBurn(_from, _value, msg.sender); } - //ERC165 and 7805 supports - /// @notice Query whether an interface id is supported (ERC-165 style). - /// @param interfaceId The interface id to query. - - function supportInterface(bytes4 interfaceId) internal { - ERC7802Storage storage s = getStorage(); - if (interfaceId != 0x33331994 || interfaceId == 0x01ffc9a7) revert ERC7802InvalidInterfaceId(interfaceId); - - s._supportedInterfaces[interfaceId] = true; - - emit Interface(interfaceId); - } - + // @notice Add a trusted bridge address. Owner-only. /// @param _bridge The bridge address to add. function addTrustedBridges(address _bridge) internal { - ERC7802Storage storage s = getStorage(); + ERC20BridgeableStorage storage s = getStorage(); - if (msg.sender != s.owner) revert ERC7802InvalidOwner(msg.sender); - s._trustedBridges[_bridge] = true; + if (msg.sender != s.owner) revert ERC20InvalidOwner(msg.sender); + s.trustedBridges[_bridge] = true; } /// @notice Remove a trusted bridge address. Owner-only. /// @param _bridge The bridge address to remove. function removeTrustedBridges(address _bridge) internal { - ERC7802Storage storage s = getStorage(); + ERC20BridgeableStorage storage s = getStorage(); - if (msg.sender != s.owner) revert ERC7802InvalidOwner(msg.sender); - s._trustedBridges[_bridge] = false; + if (msg.sender != s.owner) revert ERC20InvalidOwner(msg.sender); + s.trustedBridges[_bridge] = false; } /// @notice Internal check to check if the bridge is trusted. @@ -153,9 +141,9 @@ library LibERC7802 { /// @param _caller The address to validate function _checkTokenBridge(address _caller) internal { - ERC7802Storage storage s = getStorage(); + ERC20BridgeableStorage storage s = getStorage(); - if (_caller == address(0)) revert ERC7802InvalidBridgeAccount(address(0)); - if (s._trustedBridges[_caller] == false) revert ERC7802InvalidBridgeAccount(_caller); + if (_caller == address(0)) revert ERC20InvalidBridgeAccount(address(0)); + if (s.trustedBridges[_caller] == false) revert ERC20InvalidBridgeAccount(_caller); } } From bd13bebef9c3bcfd04bdcae5133b9a98e5177c3d Mon Sep 17 00:00:00 2001 From: beebozy Date: Wed, 19 Nov 2025 01:00:06 -0800 Subject: [PATCH 4/8] ERC20Bridgeable: Rename the folder and refactor the code --- README.md | 2 +- src/ERC20Bridgeable/ERC20BridgeableFacet.sol | 175 ++++++++++++++++++ .../libraries/LibERC20Bridgeable.sol} | 130 +++++++------ src/ERC7802/ERC20BridgeableFacet.sol | 136 -------------- 4 files changed, 245 insertions(+), 198 deletions(-) create mode 100644 src/ERC20Bridgeable/ERC20BridgeableFacet.sol rename src/{ERC7802/libraries/LibERC7802.sol => ERC20Bridgeable/libraries/LibERC20Bridgeable.sol} (53%) delete mode 100644 src/ERC7802/ERC20BridgeableFacet.sol diff --git a/README.md b/README.md index 243f6927..3c5a6ccb 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ If this feature ban breaks your mind, just realize that this smart contract libr 4. ### No public or private or internal variables - No contract or library may have storage variables declared private or public or internal. For example: `uint256 public counter;`. These visibility labels are not needed because the library uses ERC-8042 Diamond Storage throughout. This restriction does not apply to constants or immutable variables, which may be declared `internal`. + No contract or library may have storage variables declared private or public or internal. For example: `uint256 public counter;`. These visibility labels are not needed because the library uses ERC-8042 Diamond storage s1hroughout. This restriction does not apply to constants or immutable variables, which may be declared `internal`. 5. ### No private or public functions diff --git a/src/ERC20Bridgeable/ERC20BridgeableFacet.sol b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol new file mode 100644 index 00000000..3f894c12 --- /dev/null +++ b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol @@ -0,0 +1,175 @@ + // SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/// @title ERC20Bridgeable — ERC-7802-like Implementation Facet +/// @notice Provides functions and storage layout for ERC20-Bridgeable token logic. +/// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions +contract ERC20BridgeableFacet { + /// @notice Revert when a provided receiver is invalid(e.g,zero address) . + /// @param _receiver The invalid reciever address. + error ERC20InvalidReciever(address _receiver); + + /// @notice Thrown when the sender address is invalid (e.g., zero address). + /// @param _sender The invalid sender address. + + error ERC20InvalidSender(address _sender); + + /// @notice Revert when caller is not a trusted bridge. + /// @param _caller The unauthorized caller. + error ERC20InvalidBridgeAccount(address _caller); + + // @notice Revert when caller address is invalid. + /// @param _caller is the invalid address. + error ERC20InvalidCallerAddress(address _caller); + + /// @notice Revert when the owner is invalid . + /// @param _bridge The invalid address. + error ERC20InvalidOwner(address _bridge); + + error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); + /// @notice Emitted when tokens are minted via a cross-chain bridge. + /// @param _to The recipient of minted tokens. + /// @param _amount The amount minted. + /// @param _sender The bridge account that triggered the mint (msg.sender). + + event CrosschainMint(address indexed _to, uint256 _amount, address indexed _sender); + + /// @notice Emitted when a crosschain transfer burns tokens. + /// @param _from Address of the account tokens are being burned from. + /// @param _amount Amount of tokens burned. + /// @param _sender Address of the caller (msg.sender) who invoked crosschainBurn. + event CrosschainBurn(address indexed _from, uint256 _amount, address indexed _sender); + + /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 for storage location standardization + /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. + bytes32 constant STORAGE_POSITION = keccak256("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; + } + + /** + * @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 + } + } + + bytes32 constant STORAGE_POSITION1 = keccak256("compose.owner"); + + /// @custom:storage-location erc8042:compose.owner + struct OwnerStorage { + address owner; + } + + /// @notice Returns a pointer to the ERC-173 storage struct. + /// @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + /// @return s1 The OwnerStorage struct in storage. + function getStorage1() internal pure returns (OwnerStorage storage s1) { + bytes32 position = STORAGE_POSITION1; + assembly { + s1.slot := position + } + } + + /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 as template + + bytes32 constant STORAGE_POSITION2 = keccak256("compose.erc20.bridgeable"); + + struct ERC20BridgeableStorage { + mapping(address => bool) trustedBridges; + } + + function getStorage2() internal pure returns (ERC20BridgeableStorage storage s2) { + bytes32 position = STORAGE_POSITION2; + + assembly { + s2.slot := position + } + } + + /// @notice Internal crosschain mint logic. MUST be called only after validating caller. + /// @dev Increases totalSupply and recipient balance and emits CrosschainMint. + /// @param _account The account to mint tokens to. + /// @param _value The amount to mint. + + function crosschainMint(address _account, uint256 _value) external { + ERC20Storage storage s = getStorage(); + ERC20BridgeableStorage storage s2 = getStorage2(); + + if (s2.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); + if (_account == address(0)) revert ERC20InvalidReciever(address(0)); + + unchecked { + s.totalSupply += _value; + s.balanceOf[_account] += _value; + } + + emit CrosschainMint(_account, _value, msg.sender); + } + + /// @notice Internal crosschain burn logic. MUST be called only after validating caller. + /// @dev Decreases totalSupply and the `from` balance and emits CrosschainBurn. + /// @param _from The account to burn tokens from. + /// @param _value The amount to burn. + function crosschainBurn(address _from, uint256 _value) external { + ERC20Storage storage s = getStorage(); + ERC20BridgeableStorage storage s2 = getStorage2(); + + if (s2.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); + if (_from == address(0)) revert ERC20InvalidReciever(address(0)); + + uint256 accountBalance = s.balanceOf[_from]; + + if (accountBalance < _value) revert ERC20InsufficientBalance(_from, accountBalance, _value); + + unchecked { + s.totalSupply -= _value; + s.balanceOf[_from] -= _value; + } + + emit CrosschainBurn(_from, _value, msg.sender); + } + + // @notice Add a trusted bridge address. Owner-only. + /// @param _bridge The bridge address to add. + function addTrustedBridges(address _bridge) external { + ERC20BridgeableStorage storage s2 = getStorage2(); + OwnerStorage storage s1 = getStorage1(); + if (msg.sender != s1.owner) revert ERC20InvalidOwner(msg.sender); + s2.trustedBridges[_bridge] = true; + } + + /// @notice Remove a trusted bridge address. Owner-only. + /// @param _bridge The bridge address to remove. + function removeTrustedBridges(address _bridge) external { + ERC20BridgeableStorage storage s2 = getStorage2(); + OwnerStorage storage s1 = getStorage1(); + if (msg.sender != s1.owner) revert ERC20InvalidOwner(msg.sender); + s2.trustedBridges[_bridge] = false; + } + + /// @notice Internal check to check if the bridge is trusted. + /// @dev Reverts if caller is zero or not in the trusted bridges mapping. + /// @param _caller The address to validate + + function checkTokenBridge(address _caller) external { + ERC20BridgeableStorage storage s2 = getStorage2(); + + if (_caller == address(0)) revert ERC20InvalidBridgeAccount(address(0)); + if (s2.trustedBridges[_caller] == false) revert ERC20InvalidBridgeAccount(_caller); + } +} diff --git a/src/ERC7802/libraries/LibERC7802.sol b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol similarity index 53% rename from src/ERC7802/libraries/LibERC7802.sol rename to src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol index 7a8a51d1..4bf8643a 100644 --- a/src/ERC7802/libraries/LibERC7802.sol +++ b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; -/// @title LibERC20 — ERC-7802 Library +/// @title LibERC20Bridgeable — ERC-7802 like Library /// @notice Provides internal functions and storage layout for ERC-7802 token logic. /// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions library LibERC20Bridgeable { @@ -22,57 +22,82 @@ library LibERC20Bridgeable { /// @param _caller is the invalid address. error ERC20InvalidCallerAddress(address _caller); + /// @notice Revert when the owner is invalid . + /// @param _bridge The invalid address. error ERC20InvalidOwner(address _bridge); - // @notice Revert when an interface id is not supported. // the interfaces supported are IERC165 and 7805 - /// @param interfaceId The unsupported interface id. - error ERC20InvalidInterfaceId(bytes4 _interfaceId); - error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); /// @notice Emitted when tokens are minted via a cross-chain bridge. - /// @param to The recipient of minted tokens. - /// @param amount The amount minted. - /// @param sender The bridge account that triggered the mint (msg.sender). + /// @param _to The recipient of minted tokens. + /// @param _amount The amount minted. + /// @param _sender The bridge account that triggered the mint (msg.sender). event CrosschainMint(address indexed _to, uint256 _amount, address indexed _sender); /// @notice Emitted when a crosschain transfer burns tokens. - /// @param from Address of the account tokens are being burned from. - /// @param amount Amount of tokens burned. - /// @param sender Address of the caller (msg.sender) who invoked crosschainBurn. + /// @param _from Address of the account tokens are being burned from. + /// @param _amount Amount of tokens burned. + /// @param _sender Address of the caller (msg.sender) who invoked crosschainBurn. event CrosschainBurn(address indexed _from, uint256 _amount, address indexed _sender); - /// @notice Mint tokens through a crosschain transfer. - /// @param to Address to mint tokens to. - /// @param amount Amount of tokens to mint. + /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 for storage location standardization + /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. + bytes32 constant STORAGE_POSITION = keccak256("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; + } + + /** + * @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 + } + } + + bytes32 constant STORAGE_POSITION1 = keccak256("compose.owner"); - event Transfer(address indexed _from, address indexed _to, uint256 _amount); + /// @custom:storage-location erc8042:compose.owner + struct OwnerStorage { + address owner; + } - /// @notice Emitted when there is a valid supported interface call - ///@param InterfaceId The supported interface identifier. - event Interface(bytes4 indexed _InterfaceId); + /// @notice Returns a pointer to the ERC-173 storage struct. + /// @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + /// @return s1 The OwnerStorage struct in storage. + function getStorage1() internal pure returns (OwnerStorage storage s1) { + bytes32 position = STORAGE_POSITION1; + assembly { + s1.slot := position + } + } - /// @notice Storage slot for ERC-7802 using ERC8042 for storage location standardization + /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 as template - bytes32 constant STORAGE_POSITION = keccak256("compose.erc7802"); + bytes32 constant STORAGE_POSITION2 = keccak256("compose.erc20.bridgeable"); struct ERC20BridgeableStorage { - address owner; - uint256 totalSupply; - mapping(address owner => uint256 balance) balanceOf; mapping(address => bool) trustedBridges; - mapping(bytes4 => bool) supportedInterfaces; } - /// @notice Return storage pointer to ERC7802Storage - /// @dev Inline assembly to point storage to a fixed slot. - /// @return s Pointer to storage struct. - - function getStorage() internal pure returns (ERC20BridgeableStorage storage s) { - bytes32 position = STORAGE_POSITION; + function getStorage2() internal pure returns (ERC20BridgeableStorage storage s2) { + bytes32 position = STORAGE_POSITION2; assembly { - s.slot := position + s2.slot := position } } @@ -81,10 +106,11 @@ library LibERC20Bridgeable { /// @dev Increases totalSupply and recipient balance and emits CrosschainMint. /// @param _account The account to mint tokens to. /// @param _value The amount to mint. - function crooschainMint(address _account, uint256 _value) internal { - ERC20BridgeableStorage storage s = getStorage(); + function crosschainMint(address _account, uint256 _value) internal { + ERC20Storage storage s = getStorage(); + ERC20BridgeableStorage storage s2 = getStorage2(); - if (s._trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); + if (s2.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); if (_account == address(0)) revert ERC20InvalidReciever(address(0)); unchecked { @@ -100,9 +126,10 @@ library LibERC20Bridgeable { /// @param _from The account to burn tokens from. /// @param _value The amount to burn. function crosschainBurn(address _from, uint256 _value) internal { - ERC20BridgeableStorage s = getStorage(); - - if (s._trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); + ERC20Storage storage s = getStorage(); + ERC20BridgeableStorage storage s2 = getStorage2(); + + if (s2.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); if (_from == address(0)) revert ERC20InvalidReciever(address(0)); uint256 accountBalance = s.balanceOf[_from]; @@ -116,34 +143,15 @@ library LibERC20Bridgeable { emit CrosschainBurn(_from, _value, msg.sender); } - - - // @notice Add a trusted bridge address. Owner-only. - /// @param _bridge The bridge address to add. - function addTrustedBridges(address _bridge) internal { - ERC20BridgeableStorage storage s = getStorage(); - - if (msg.sender != s.owner) revert ERC20InvalidOwner(msg.sender); - s.trustedBridges[_bridge] = true; - } - - /// @notice Remove a trusted bridge address. Owner-only. - /// @param _bridge The bridge address to remove. - function removeTrustedBridges(address _bridge) internal { - ERC20BridgeableStorage storage s = getStorage(); - - if (msg.sender != s.owner) revert ERC20InvalidOwner(msg.sender); - s.trustedBridges[_bridge] = false; - } - - /// @notice Internal check to check if the bridge is trusted. + /// @notice Internal check to check if the bridge is trusted. /// @dev Reverts if caller is zero or not in the trusted bridges mapping. /// @param _caller The address to validate - function _checkTokenBridge(address _caller) internal { - ERC20BridgeableStorage storage s = getStorage(); + + function checkTokenBridge(address _caller) internal { + ERC20BridgeableStorage storage s2 = getStorage2(); if (_caller == address(0)) revert ERC20InvalidBridgeAccount(address(0)); - if (s.trustedBridges[_caller] == false) revert ERC20InvalidBridgeAccount(_caller); + if (s2.trustedBridges[_caller] == false) revert ERC20InvalidBridgeAccount(_caller); } } diff --git a/src/ERC7802/ERC20BridgeableFacet.sol b/src/ERC7802/ERC20BridgeableFacet.sol deleted file mode 100644 index 254defb2..00000000 --- a/src/ERC7802/ERC20BridgeableFacet.sol +++ /dev/null @@ -1,136 +0,0 @@ - // SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -contract ERC20BridgeableFacet { - /// @notice Revert when a provided receiver is invalid(e.g,zero address) . - /// @param _receiver The invalid reciever address. - error ERC20InvalidReciever(address _receiver); - - /// @notice Thrown when the sender address is invalid (e.g., zero address). - /// @param _sender The invalid sender address. - - error ERC20InvalidSender(address _sender); - - /// @notice Revert when caller is not a trusted bridge. - /// @param _caller The unauthorized caller. - error ERC20InvalidBridgeAccount(address _caller); - - // @notice Revert when caller address is invalid. - /// @param _caller is the invalid address. - error ERC20InvalidCallerAddress(address _caller); - - error ERC20InvalidOwner(address _bridge); - - // @notice Revert when an interface id is not supported. // the interfaces supported are IERC165 and 7805 - /// @param interfaceId The unsupported interface id. - error ERC20InvalidInterfaceId(bytes4 _interfaceId); - - error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); - /// @notice Emitted when tokens are minted via a cross-chain bridge. - /// @param to The recipient of minted tokens. - /// @param amount The amount minted. - /// @param sender The bridge account that triggered the mint (msg.sender). - - event CrosschainMint(address indexed _to, uint256 _amount, address indexed _sender); - - /// @notice Emitted when a crosschain transfer burns tokens. - /// @param from Address of the account tokens are being burned from. - /// @param amount Amount of tokens burned. - /// @param sender Address of the caller (msg.sender) who invoked crosschainBurn. - event CrosschainBurn(address indexed _from, uint256 _amount, address indexed _sender); - - /// @notice Mint tokens through a crosschain transfer. - /// @param to Address to mint tokens to. - /// @param amount Amount of tokens to mint. - - event Transfer(address indexed _from, address indexed _to, uint256 _amount); - - /// @notice Emitted when an interface ID is registered or supported. - /// @param interfaceId The supported interface identifier. - event Interface(bytes4 indexed _interfaceId); - - /// @notice Storage slot for ERC-7802 using ERC8042 - - bytes32 constant STORAGE_POSITION = keccak256("compose.erc7802"); - - struct ERC20BridgeableStorage { - address owner; - uint256 totalSupply; - mapping(address owner => uint256 balance) balanceOf; - mapping(address => bool) trustedBridges; - mapping(bytes4 => bool) supportedInterfaces; - } - - /// @notice Return storage pointer to ERC7802Storage - /// @dev Inline assembly to point storage to a fixed slot. - /// @return s Pointer to storage struct. - - function getStorage() internal pure returns (ERC20BridgeableStorage storage s) { - bytes32 position = STORAGE_POSITION; - - assembly { - s.slot := position - } - } - - function getOwner() external view returns (address) { - return getStorage().owner; - } - - /// @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 Internal crosschain mint logic. MUST be called only after validating caller. - /// @dev Increases totalSupply and recipient balance and emits CrosschainMint. - /// @param _account The account to mint tokens to. - /// @param _value The amount to mint. - function crosschainMint(address _account, uint256 _value) external { - ERC20BridgeableStorage storage s = getStorage(); - - if (s.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); - if (_account == address(0)) revert ERC20InvalidReciever(address(0)); - - unchecked { - s.totalSupply += _value; - s.balanceOf[_account] += _value; - } - - emit CrosschainMint(_account, _value, msg.sender); - } - - /// @notice Internal crosschain burn logic. MUST be called only after validating caller. - /// @dev Decreases totalSupply and the `from` balance and emits CrosschainBurn. - /// @param _from The account to burn tokens from. - /// @param _value The amount to burn. - function crosschainBurn(address _from, uint256 _value) external { - EERC20BridgeableStorage storage s = getStorage(); - - if (s.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); - if (_from == address(0)) revert ERC20InvalidReciever(address(0)); - - uint256 accountBalance = s.balanceOf[_from]; - - if (accountBalance < _value) revert ERC20InsufficientBalance(_from, accountBalance, _value); - - unchecked { - s.totalSupply -= _value; - s.balanceOf[_from] -= _value; - } - - emit CrosschainBurn(_from, _value, msg.sender); - } - - -} From 7927d5f7a644ccba80258fa8633e6a60b9d702b4 Mon Sep 17 00:00:00 2001 From: beebozy Date: Thu, 20 Nov 2025 13:54:46 -0800 Subject: [PATCH 5/8] ERC20Bridgeable: refactoring of the code --- README.md | 140 +----------------- src/ERC20Bridgeable/ERC20BridgeableFacet.sol | 110 ++++++-------- .../libraries/LibERC20Bridgeable.sol | 94 +++++------- 3 files changed, 84 insertions(+), 260 deletions(-) diff --git a/README.md b/README.md index c69916dc..49925fc2 100644 --- a/README.md +++ b/README.md @@ -45,145 +45,7 @@ forge build # Run tests forge test -4. ### No public or private or internal variables - - No contract or library may have storage variables declared private or public or internal. For example: `uint256 public counter;`. These visibility labels are not needed because the library uses ERC-8042 Diamond storage s1hroughout. This restriction does not apply to constants or immutable variables, which may be declared `internal`. - -5. ### No private or public functions - - No contract or library may have a function declared private or public. For example: `function approve(address _spender, uint256 _value) private { ...`. This means all functions in contracts must be declared `internal` or `external`. - -6. ### No external functions in Solidity libraries - - No Solidity library may have any external functions. For example: `function name() external view returns (string memory)`. All functions in Solidity libraries must be declared `internal`. - -7. ### No `using for` in Solidity libraries - - No Solidity library may use the `using` directive. For example: `using LibSomething for uint`. - -8. ### No `selfdestruct`. - - No contract or library may use `selfdestruct`. - -Other Solidity features will likely be added to this ban list. - -**Note** that the feature ban applies to the smart contracts and libraries within Compose. It does not apply to the users that use Compose. Users can do what they want to do and it is our job to help them. - -## Purpose of Compose - -The purpose of Compose is to help people create smart contract systems. We want to help them do that quickly, securely, confidently, with understanding, and with the functionality they want. Nothing is more important than this purpose. - -## Vision - -Compose is an effort to apply software engineering principles specifically to a smart contract library. Smart contracts are not like other software, so let's not treat them like other software. We need to re-evaluate knowledge of programming and software engineering specifically as it applies to smart contracts. Let's really look at what smart contracts are and design and write our library for specifically what we are dealing with. - -What we are dealing with: - -1. **Smart contracts are immutable.** Once deployed, the source code for a smart contract doesn't change. -2. **Smart contracts are forever.** Once deployed, smart contracts can run or exist forever. -3. **Smart contracts are shared.** Once deployed, smart contracts can be seen and accessed by anyone. -4. **Smart contracts run on a distributed network.** Once deployed, smart contracts are running within the capabilities and constraints of the Ethereum Virtual Machine (EVM) and the blockchain network it is deployed on. -5. **Smart contracts must be secure.** Once deployed, there can be very serious consequences if there is a bug or security vulnerability in a smart contract. -6. **Smart contracts are written in a specific language** In our case Compose is written in the Solidity programming language. - -If we gather all knowledge about programming and software engineering that has ever existed and will exist, including what you know and what you will soon learn or know, and we evaluate that knowledge as it can best apply specifically to a smart contract library, to create the best smart contract library possible, what do we end up with? Hopefully we end up with what Compose becomes. - -## Design - -The design and implementation of Compose is based on the following design principles. - -1. ### Understanding - This is the top design and guiding principle of this project. We help our users *understand* the things they want to know so they can *confidently* achieve what they are trying to do. This is why we must have very good documentation, and why we write easy to read and understand code. Understanding leads to solutions, creates confidence, kills bugs and gets things done. Understanding is everything. So we nurture it and create it. - -1. ### The code is written to be read - The code in this library is written to be read and understood by others easily. We want our users to understand our library and be confident with it. We help them do that with code that is easy to read and understand. - - We hope thousands of smart contract systems use our smart contracts. We say in advance to thousands of people in the future, over tens or hundreds of years, who are reading the verified source code of deployed smart contract systems that use our library, **YOU'RE WELCOME**, for making it easy to read and understand. - -1. ### Repeat yourself - The DRY principle — *Don’t Repeat Yourself* — is a well-known rule in software development. We **intentionally** break that rule. - - In traditional software, DRY reduces duplication and makes it easier to update multiple parts of a program by changing one section of code. But deployed smart contracts *don’t change*. DRY can actually reduce clarity. Every internal function adds another indirection that developers must trace through, and those functions sometimes introduce extra logic for different cases. Repetition can make smart contracts easier to read and reason about. - - That said, DRY still has its place. When a large block of code performs a complete, self-contained action and is used identically in multiple locations, moving it into an internal function can improve readability. For example, Compose's ERC-721 implementation uses an `internalTransferFrom` function to eliminate duplication while keeping the code easy to read and understand. - - **Guideline:** Repeat yourself when it makes your code easier to read and understand. Use DRY sparingly and only to make code more readable by removing a lot of unnecessary duplication. - -1. ### Compose diamonds - - A diamond contract is a smart contract that gets its functionality from other contracts called facets. You can add, replace, or remove functionality from these facets, which lets the diamond contract change or grow without deploying a completely new contract. This design makes it easier to build smart contracts that are modular (made of separate parts) and composable (able to work together in flexible ways). A diamond contract can be deployed and then incrementally developed by adding/replacing/removing functionality over time. Diamond contracts can be upgradeable or immutable. [ERC-2535 Diamonds](https://eips.ethereum.org/EIPS/eip-2535) is the standard that defines how diamond contracts work. - - Compose is specifically designed to help users develop and deploy [diamond contracts](https://eips.ethereum.org/EIPS/eip-2535). A major part of this project is creating an onchain diamond factory that makes it easy to deploy diamonds that use facets provided by this library and elsewhere. - - Much of Compose consists of facets and Solidity libraries that are used by users to create diamond contracts. - -1. ### Onchain composability - - We design facets for maximum onchain reusability and composability. - - We plan to deploy the facets written in this library to many blockchains. There's no reason to take our Solidity source code, as is, and deploy it yourself to a blockchain if it is already deployed there. Just use the facets that are already deployed. We will maintain lists of blockchain addresses for facets that are deployed. - - For example if you want a diamond contract with standard ERC721 NFT functionality, then deploy a diamond contract using this library and add the ERC721 functionality from the existing, already deployed ERC721 facet. You do not need to deploy an ERC721 facet from this library if it has already been deployed to the blockchain you are using. - - Users also have the option of taking our facet source code and modifying it for their needs and deploying what they wish. - -1. ### Favor onchain composition over inheritance - - > Favoring onchain composition over inheritance means designing blockchain-based systems by building them from smaller, independent components that are combined, rather than inheriting functionality from a large, parent class. This approach creates more flexible, loosely coupled, and maintainable smart contracts, as components can be easily swapped or reused without the rigid dependencies that inheritance introduces. It is a software design principle that emphasizes a "has-a" relationship (composition) over an "is-a" relationship (inheritance). - - One of the reasons that inheritance is banned in the library is because onchain composition is favored over inheritance. This is a newer idea that wasn't very possible before diamond contracts. Instead of inheriting a contract to give it additional functionality, just make a new contract (facet), deploy it, and add its functions to your diamond. - - #### Example - - Let's say you are making an onchain game that has its own NFTs with standard NFT (ERC721) functionality, plus additional custom NFT functionality. Here are steps you could take: - - 1. Develop a new facet with the custom NFT functionality that you want. You can use the `LibERC721` Solidity library provided by Compose to access NFT storage. If needed you also create your own diamond storage for your custom functionality in your facet. - - 2. Deploy your new facet with custom NFT functionality. - - 3. Using Compose, setup the deployment of your diamond contract so that it adds the standard NFT functions from the existing, already deployed ERC721 facet (which was deployed by Compose), and also adds the functions from your custom NFT facet. - - 4. Deploy your diamond! - - If you need to modify the functionality of standard ERC721 functions, then in that case you cannot use onchain composition. You can make your own custom ERC721 facet by copying the `ERC721Facet.sol` file in Compose and make the necessary changes, or you can inherit the `ERC721Facet`. - -1. ### Maintain compatibility with existing standards, libraries, and systems - - We want things we build to interoperate and be compatible with existing tools, systems, and expectations. So when writing a smart contract, or particular functionality, find out if there are implementation details that are already established that affect how the functionality works, and make sure your implementation works the way that will be expected. I'm not talking about how the code is written, but how it works, how it functions. We can write our code better (more clear, more readable, and better documented), but make it function the same as established smart contract functionality. - - When implementing new functionality, here are some things you need to consider and do to ensure interoperability and to meet existing expectations of functionality: - - 1. Are there any [ERC standards](https://eips.ethereum.org/erc) that cover the functionality? If so, should probably follow that. - 2. Has an existing established library such as [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable) already implemented that functionality in their library? Make sure your version functions the same -- emits the same events, issues the same error messages, reverts when it reverts, etc. Generally we want to match existing widespread adopted functionality. We don't want to surprise our users, unless it is a good surprise. - 3. Are there existing widespread systems, (for example OpenSea, other NFT exchanges, and DAO and voting systems), which expect contracts to function a certain way? Match it. - - - ## Contributors - - New contributors are welcome. Choose the [issues](https://github.com/Perfect-Abstractions/Compose/issues) you want to work on and leave comments describing what you want to do and how you want to do it. I'll answer you and assign you to issues and you can start. - - Look at the [ERC20 and ERC721 implementations](./src/) to see examples of how things are written in this library. - - Once you are assigned to an issue you can fork the repository, implement what you are working on, then submit a pull request and I will review it and merge it and/or give you feedback on the work. - - You can also make new issues to suggest new functionality or work. - - If you have contribution or development questions then please contact me or create an issue. The discord for Compose is here: https://discord.gg/DCBD2UKbxc - - This is the beginning and we are still working out how this will all work. I am glad you are interested in this project and I want to make something great with you. - - -Nick - - - - - -## Usage - -### Build - -```shell -$ forge build +# For test documentation, see test/README.md ``` ## Documentation diff --git a/src/ERC20Bridgeable/ERC20BridgeableFacet.sol b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol index 3f894c12..62835250 100644 --- a/src/ERC20Bridgeable/ERC20BridgeableFacet.sol +++ b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol @@ -1,4 +1,4 @@ - // SPDX-License-Identifier: MIT + // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; /// @title ERC20Bridgeable — ERC-7802-like Implementation Facet @@ -22,10 +22,6 @@ contract ERC20BridgeableFacet { /// @param _caller is the invalid address. error ERC20InvalidCallerAddress(address _caller); - /// @notice Revert when the owner is invalid . - /// @param _bridge The invalid address. - error ERC20InvalidOwner(address _bridge); - error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); /// @notice Emitted when tokens are minted via a cross-chain bridge. /// @param _to The recipient of minted tokens. @@ -42,7 +38,7 @@ contract ERC20BridgeableFacet { /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 for storage location standardization /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. - bytes32 constant STORAGE_POSITION = keccak256("compose.erc20"); + bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); /** * @dev ERC-8042 compliant storage struct for ERC20 token data. @@ -61,43 +57,25 @@ contract ERC20BridgeableFacet { * @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; + function erc20Storage() internal pure returns (ERC20Storage storage s) { + bytes32 position = ERC20_STORAGE_POSITION; assembly { s.slot := position } } + /// @notice Storage POSITION for ERC-20 Bridgeable using ERC8042 as template - bytes32 constant STORAGE_POSITION1 = keccak256("compose.owner"); - - /// @custom:storage-location erc8042:compose.owner - struct OwnerStorage { - address owner; - } - - /// @notice Returns a pointer to the ERC-173 storage struct. - /// @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. - /// @return s1 The OwnerStorage struct in storage. - function getStorage1() internal pure returns (OwnerStorage storage s1) { - bytes32 position = STORAGE_POSITION1; - assembly { - s1.slot := position - } - } - - /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 as template - - bytes32 constant STORAGE_POSITION2 = keccak256("compose.erc20.bridgeable"); + bytes32 constant BRIDGEABLE_STORAGE_POSITION = keccak256("compose.erc20.bridgeable"); struct ERC20BridgeableStorage { mapping(address => bool) trustedBridges; } - function getStorage2() internal pure returns (ERC20BridgeableStorage storage s2) { - bytes32 position = STORAGE_POSITION2; + function bridgeStorage() internal pure returns (ERC20BridgeableStorage storage s) { + bytes32 position = BRIDGEABLE_STORAGE_POSITION; assembly { - s2.slot := position + s.slot := position } } @@ -107,15 +85,19 @@ contract ERC20BridgeableFacet { /// @param _value The amount to mint. function crosschainMint(address _account, uint256 _value) external { - ERC20Storage storage s = getStorage(); - ERC20BridgeableStorage storage s2 = getStorage2(); + ERC20Storage storage erc20 = erc20Storage(); + ERC20BridgeableStorage storage bridge = bridgeStorage(); - if (s2.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); - if (_account == address(0)) revert ERC20InvalidReciever(address(0)); + if (bridge.trustedBridges[msg.sender] == false) { + revert ERC20InvalidBridgeAccount(msg.sender); + } + if (_account == address(0)) { + revert ERC20InvalidReciever(address(0)); + } unchecked { - s.totalSupply += _value; - s.balanceOf[_account] += _value; + erc20.totalSupply += _value; + erc20.balanceOf[_account] += _value; } emit CrosschainMint(_account, _value, msg.sender); @@ -126,50 +108,42 @@ contract ERC20BridgeableFacet { /// @param _from The account to burn tokens from. /// @param _value The amount to burn. function crosschainBurn(address _from, uint256 _value) external { - ERC20Storage storage s = getStorage(); - ERC20BridgeableStorage storage s2 = getStorage2(); - - if (s2.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); - if (_from == address(0)) revert ERC20InvalidReciever(address(0)); + ERC20Storage storage erc20 = erc20Storage(); + ERC20BridgeableStorage storage bridge = bridgeStorage(); + + if (bridge.trustedBridges[msg.sender] == false) { + revert ERC20InvalidBridgeAccount(msg.sender); + } + if (_from == address(0)) { + revert ERC20InvalidReciever(address(0)); + } - uint256 accountBalance = s.balanceOf[_from]; + uint256 accountBalance = erc20.balanceOf[_from]; - if (accountBalance < _value) revert ERC20InsufficientBalance(_from, accountBalance, _value); + if (accountBalance < _value) { + revert ERC20InsufficientBalance(_from, accountBalance, _value); + } unchecked { - s.totalSupply -= _value; - s.balanceOf[_from] -= _value; + erc20.totalSupply -= _value; + erc20.balanceOf[_from] -= _value; } emit CrosschainBurn(_from, _value, msg.sender); } - // @notice Add a trusted bridge address. Owner-only. - /// @param _bridge The bridge address to add. - function addTrustedBridges(address _bridge) external { - ERC20BridgeableStorage storage s2 = getStorage2(); - OwnerStorage storage s1 = getStorage1(); - if (msg.sender != s1.owner) revert ERC20InvalidOwner(msg.sender); - s2.trustedBridges[_bridge] = true; - } - - /// @notice Remove a trusted bridge address. Owner-only. - /// @param _bridge The bridge address to remove. - function removeTrustedBridges(address _bridge) external { - ERC20BridgeableStorage storage s2 = getStorage2(); - OwnerStorage storage s1 = getStorage1(); - if (msg.sender != s1.owner) revert ERC20InvalidOwner(msg.sender); - s2.trustedBridges[_bridge] = false; - } - /// @notice Internal check to check if the bridge is trusted. /// @dev Reverts if caller is zero or not in the trusted bridges mapping. /// @param _caller The address to validate - function checkTokenBridge(address _caller) external { - ERC20BridgeableStorage storage s2 = getStorage2(); + function checkTokenBridge(address _caller) external view { + ERC20BridgeableStorage storage bridge = bridgeStorage(); - if (_caller == address(0)) revert ERC20InvalidBridgeAccount(address(0)); - if (s2.trustedBridges[_caller] == false) revert ERC20InvalidBridgeAccount(_caller); + if (_caller == address(0)) { + revert ERC20InvalidBridgeAccount(address(0)); + } + if (bridge.trustedBridges[_caller] == false) { + revert ERC20InvalidBridgeAccount(_caller); + } } } diff --git a/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol index 4bf8643a..e3f5a594 100644 --- a/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol +++ b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol @@ -22,10 +22,6 @@ library LibERC20Bridgeable { /// @param _caller is the invalid address. error ERC20InvalidCallerAddress(address _caller); - /// @notice Revert when the owner is invalid . - /// @param _bridge The invalid address. - error ERC20InvalidOwner(address _bridge); - error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); /// @notice Emitted when tokens are minted via a cross-chain bridge. /// @param _to The recipient of minted tokens. @@ -42,7 +38,7 @@ library LibERC20Bridgeable { /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 for storage location standardization /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. - bytes32 constant STORAGE_POSITION = keccak256("compose.erc20"); + bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); /** * @dev ERC-8042 compliant storage struct for ERC20 token data. @@ -61,43 +57,25 @@ library LibERC20Bridgeable { * @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; + function erc20Storage() internal pure returns (ERC20Storage storage s) { + bytes32 position = ERC20_STORAGE_POSITION; assembly { s.slot := position } } - - bytes32 constant STORAGE_POSITION1 = keccak256("compose.owner"); - - /// @custom:storage-location erc8042:compose.owner - struct OwnerStorage { - address owner; - } - - /// @notice Returns a pointer to the ERC-173 storage struct. - /// @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. - /// @return s1 The OwnerStorage struct in storage. - function getStorage1() internal pure returns (OwnerStorage storage s1) { - bytes32 position = STORAGE_POSITION1; - assembly { - s1.slot := position - } - } - /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 as template - bytes32 constant STORAGE_POSITION2 = keccak256("compose.erc20.bridgeable"); + bytes32 constant BRIDGEABLE_STORAGE_POSITION = keccak256("compose.erc20.bridgeable"); struct ERC20BridgeableStorage { mapping(address => bool) trustedBridges; } - function getStorage2() internal pure returns (ERC20BridgeableStorage storage s2) { - bytes32 position = STORAGE_POSITION2; + function bridgeStorage() internal pure returns (ERC20BridgeableStorage storage s) { + bytes32 position = BRIDGEABLE_STORAGE_POSITION; assembly { - s2.slot := position + s.slot := position } } @@ -106,16 +84,19 @@ library LibERC20Bridgeable { /// @dev Increases totalSupply and recipient balance and emits CrosschainMint. /// @param _account The account to mint tokens to. /// @param _value The amount to mint. - function crosschainMint(address _account, uint256 _value) internal { - ERC20Storage storage s = getStorage(); - ERC20BridgeableStorage storage s2 = getStorage2(); - - if (s2.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); - if (_account == address(0)) revert ERC20InvalidReciever(address(0)); + function crosschainMint(address _account, uint256 _value) internal { + ERC20Storage storage erc20 = erc20Storage(); + ERC20BridgeableStorage storage bridge = bridgeStorage(); + if (bridge.trustedBridges[msg.sender] == false) { + revert ERC20InvalidBridgeAccount(msg.sender); + } + if (_account == address(0)) { + revert ERC20InvalidReciever(address(0)); + } unchecked { - s.totalSupply += _value; - s.balanceOf[_account] += _value; + erc20.totalSupply += _value; + erc20.balanceOf[_account] += _value; } emit CrosschainMint(_account, _value, msg.sender); @@ -126,32 +107,39 @@ library LibERC20Bridgeable { /// @param _from The account to burn tokens from. /// @param _value The amount to burn. function crosschainBurn(address _from, uint256 _value) internal { - ERC20Storage storage s = getStorage(); - ERC20BridgeableStorage storage s2 = getStorage2(); - - if (s2.trustedBridges[msg.sender] == false) revert ERC20InvalidBridgeAccount(msg.sender); - if (_from == address(0)) revert ERC20InvalidReciever(address(0)); + ERC20Storage storage erc20 = erc20Storage(); + ERC20BridgeableStorage storage bridge = bridgeStorage(); - uint256 accountBalance = s.balanceOf[_from]; - - if (accountBalance < _value) revert ERC20InsufficientBalance(_from, accountBalance, _value); + if (bridge.trustedBridges[msg.sender] == false) { + revert ERC20InvalidBridgeAccount(msg.sender); + } + if (_from == address(0)) { + revert ERC20InvalidReciever(address(0)); + } + uint256 accountBalance = erc20.balanceOf[_from]; + if (accountBalance < _value) { + revert ERC20InsufficientBalance(_from, accountBalance, _value); + } unchecked { - s.totalSupply -= _value; - s.balanceOf[_from] -= _value; + erc20.totalSupply -= _value; + erc20.balanceOf[_from] -= _value; } emit CrosschainBurn(_from, _value, msg.sender); } - /// @notice Internal check to check if the bridge is trusted. + /// @notice Internal check to check if the bridge is trusted. /// @dev Reverts if caller is zero or not in the trusted bridges mapping. /// @param _caller The address to validate - - function checkTokenBridge(address _caller) internal { - ERC20BridgeableStorage storage s2 = getStorage2(); + function checkTokenBridge(address _caller) internal view { + ERC20BridgeableStorage storage bridge = bridgeStorage(); - if (_caller == address(0)) revert ERC20InvalidBridgeAccount(address(0)); - if (s2.trustedBridges[_caller] == false) revert ERC20InvalidBridgeAccount(_caller); + if (_caller == address(0)) { + revert ERC20InvalidBridgeAccount(address(0)); + } + if (bridge.trustedBridges[_caller] == false) { + revert ERC20InvalidBridgeAccount(_caller); + } } } From 0eb553cb88f045d46edc2db501fabd6246a08fe9 Mon Sep 17 00:00:00 2001 From: beebozy Date: Thu, 20 Nov 2025 12:48:23 -0800 Subject: [PATCH 6/8] ERC20Bridgeable: refactoring of the code --- src/ERC20Bridgeable/ERC20BridgeableFacet.sol | 142 ++++++++++++++---- .../libraries/LibERC20Bridgeable.sol | 94 ++++++++---- 2 files changed, 170 insertions(+), 66 deletions(-) diff --git a/src/ERC20Bridgeable/ERC20BridgeableFacet.sol b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol index 62835250..d894585e 100644 --- a/src/ERC20Bridgeable/ERC20BridgeableFacet.sol +++ b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol @@ -1,8 +1,21 @@ - // SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT pragma solidity >=0.8.30; -/// @title ERC20Bridgeable — ERC-7802-like Implementation Facet -/// @notice Provides functions and storage layout for ERC20-Bridgeable token logic. +/// @title ERC-165 Standard Interface Detection Interface +/// @notice Interface for detecting what interfaces a contract implements +/// @dev ERC-165 allows contracts to publish their supported interfaces +interface IERC165 { + /// @notice Query if a contract implements an interface + /// @param _interfaceId The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `_interfaceId` and + /// `_interfaceId` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 _interfaceId) external view returns (bool); +} + +/// @title ERC20Bridgeable — ERC-7802 Implementation Facet +/// @notice Provides functions and storage layout for ERC20-Bridgeable token logic. /// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions contract ERC20BridgeableFacet { /// @notice Revert when a provided receiver is invalid(e.g,zero address) . @@ -11,7 +24,6 @@ contract ERC20BridgeableFacet { /// @notice Thrown when the sender address is invalid (e.g., zero address). /// @param _sender The invalid sender address. - error ERC20InvalidSender(address _sender); /// @notice Revert when caller is not a trusted bridge. @@ -22,12 +34,15 @@ contract ERC20BridgeableFacet { /// @param _caller is the invalid address. error ERC20InvalidCallerAddress(address _caller); + /// @notice Unauthorized sender error from AccessControl. + error AccessControlUnauthorized(address _sender, address _account); + error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); + /// @notice Emitted when tokens are minted via a cross-chain bridge. /// @param _to The recipient of minted tokens. /// @param _amount The amount minted. /// @param _sender The bridge account that triggered the mint (msg.sender). - event CrosschainMint(address indexed _to, uint256 _amount, address indexed _sender); /// @notice Emitted when a crosschain transfer burns tokens. @@ -36,7 +51,39 @@ contract ERC20BridgeableFacet { /// @param _sender Address of the caller (msg.sender) who invoked crosschainBurn. event CrosschainBurn(address indexed _from, uint256 _amount, address indexed _sender); - /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 for storage location standardization + /// @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); + + /// ----------------------------------------------------------------------- + /// ERC165 integration (re-uses ERC165Facet storage layout) + /// ----------------------------------------------------------------------- + + /// @notice Storage slot for ERC-165 using ERC8042 for storage location standardization + /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. + bytes32 constant ERC165_STORAGE_POSITION = keccak256("compose.erc165"); + /// @notice ERC-165 storage layout using the ERC-8042 standard + /// @custom:storage-location erc8042:compose.erc165 + + struct ERC165Storage { + /// @notice Mapping of interface IDs to whether they are supported + mapping(bytes4 => bool) supportedInterfaces; + } + + function getErc165Storage() internal pure returns (ERC165Storage storage s) { + bytes32 position = ERC165_STORAGE_POSITION; + assembly { + s.slot := position + } + } + + /// ----------------------------------------------------------------------- + /// ERC20 integration (re-uses ERC20Facet storage layout) + /// ----------------------------------------------------------------------- + + /// @notice Storage slot for ERC-20 token using ERC8042 for storage location standardization /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); @@ -57,40 +104,49 @@ contract ERC20BridgeableFacet { * @dev Uses inline assembly to set the storage slot reference. * @return s The ERC20 storage struct reference. */ - function erc20Storage() internal pure returns (ERC20Storage storage s) { + function getERC20Storage() internal pure returns (ERC20Storage storage s) { bytes32 position = ERC20_STORAGE_POSITION; assembly { s.slot := position } } - /// @notice Storage POSITION for ERC-20 Bridgeable using ERC8042 as template - bytes32 constant BRIDGEABLE_STORAGE_POSITION = keccak256("compose.erc20.bridgeable"); + /// ----------------------------------------------------------------------- + /// AccessControl integration (re-uses AccessControlFacet storage layout) + /// ----------------------------------------------------------------------- - struct ERC20BridgeableStorage { - mapping(address => bool) trustedBridges; - } + /// @notice Storage slot identifier. + bytes32 constant ACCESS_STORAGE_POSITION = keccak256("compose.accesscontrol"); - function bridgeStorage() internal pure returns (ERC20BridgeableStorage storage s) { - bytes32 position = BRIDGEABLE_STORAGE_POSITION; + /// @notice storage struct for the AccessControl. + struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + } + /// @notice helper to return AccessControlStorage at its diamond slot + function getAccessControlStorage() internal pure returns (AccessControlStorage storage s) { + bytes32 position = ACCESS_STORAGE_POSITION; assembly { s.slot := position } } - /// @notice Internal crosschain mint logic. MUST be called only after validating caller. - /// @dev Increases totalSupply and recipient balance and emits CrosschainMint. + /// @notice role identifier for trusted bridge actors + bytes32 internal constant TRUSTED_BRIDGE_ROLE = keccak256("trusted-bridge"); + + /// @notice Cross-chain mint — callable only by an address having the `trusted-bridge` role. /// @param _account The account to mint tokens to. /// @param _value The amount to mint. - function crosschainMint(address _account, uint256 _value) external { - ERC20Storage storage erc20 = erc20Storage(); - ERC20BridgeableStorage storage bridge = bridgeStorage(); + ERC20Storage storage erc20 = getERC20Storage(); + + AccessControlStorage storage acs = getAccessControlStorage(); - if (bridge.trustedBridges[msg.sender] == false) { - revert ERC20InvalidBridgeAccount(msg.sender); + // authorize: caller must have the trusted-bridge role + if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { + revert AccessControlUnauthorized(msg.sender, _account); } + if (_account == address(0)) { revert ERC20InvalidReciever(address(0)); } @@ -99,21 +155,23 @@ contract ERC20BridgeableFacet { erc20.totalSupply += _value; erc20.balanceOf[_account] += _value; } - + emit Transfer(address(0), _account, _value); emit CrosschainMint(_account, _value, msg.sender); } - /// @notice Internal crosschain burn logic. MUST be called only after validating caller. - /// @dev Decreases totalSupply and the `from` balance and emits CrosschainBurn. + /// @notice Cross-chain burn — callable only by an address having the `trusted-bridge` role. /// @param _from The account to burn tokens from. /// @param _value The amount to burn. function crosschainBurn(address _from, uint256 _value) external { - ERC20Storage storage erc20 = erc20Storage(); - ERC20BridgeableStorage storage bridge = bridgeStorage(); + ERC20Storage storage erc20 = getERC20Storage(); - if (bridge.trustedBridges[msg.sender] == false) { - revert ERC20InvalidBridgeAccount(msg.sender); + AccessControlStorage storage acs = getAccessControlStorage(); + + // authorize: caller must have the trusted-bridge role + if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { + revert AccessControlUnauthorized(msg.sender, _from); } + if (_from == address(0)) { revert ERC20InvalidReciever(address(0)); } @@ -129,21 +187,39 @@ contract ERC20BridgeableFacet { erc20.balanceOf[_from] -= _value; } + emit Transfer(_from, address(0), _value); emit CrosschainBurn(_from, _value, msg.sender); } - /// @notice Internal check to check if the bridge is trusted. - /// @dev Reverts if caller is zero or not in the trusted bridges mapping. + /// @notice Internal check to check if the bridge (caller) is trusted. + /// @dev Reverts if caller is zero or not in the AccessControl `trusted-bridge` role. /// @param _caller The address to validate - function checkTokenBridge(address _caller) external view { - ERC20BridgeableStorage storage bridge = bridgeStorage(); + AccessControlStorage storage acs = getAccessControlStorage(); if (_caller == address(0)) { revert ERC20InvalidBridgeAccount(address(0)); } - if (bridge.trustedBridges[_caller] == false) { + + if (!acs.hasRole[_caller][TRUSTED_BRIDGE_ROLE]) { revert ERC20InvalidBridgeAccount(_caller); } } + + /// @notice Query if a contract implements an interface + /// @param _interfaceId The interface identifier, as specified in ERC-165 + /// @dev This function checks if the diamond supports the given interface ID + /// @return `true` if the contract implements `_interfaceId` and + /// `_interfaceId` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 _interfaceId) external view returns (bool) { + ERC165Storage storage erc165Storage = getErc165Storage(); + + // If the ERC165 interface itself is being queried, return true + // since this facet implements ERC165 + if (_interfaceId == type(IERC165).interfaceId) { + return true; + } + + return erc165Storage.supportedInterfaces[_interfaceId]; + } } diff --git a/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol index e3f5a594..0ce217a6 100644 --- a/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol +++ b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; -/// @title LibERC20Bridgeable — ERC-7802 like Library -/// @notice Provides internal functions and storage layout for ERC-7802 token logic. -/// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions +// /// @title LibERC20Bridgeable — ERC-7802 Library +// /// @notice Provides internal functions and storage layout for ERC-7802 token logic. +// /// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions library LibERC20Bridgeable { /// @notice Revert when a provided receiver is invalid(e.g,zero address) . /// @param _receiver The invalid reciever address. @@ -11,7 +11,6 @@ library LibERC20Bridgeable { /// @notice Thrown when the sender address is invalid (e.g., zero address). /// @param _sender The invalid sender address. - error ERC20InvalidSender(address _sender); /// @notice Revert when caller is not a trusted bridge. @@ -22,12 +21,15 @@ library LibERC20Bridgeable { /// @param _caller is the invalid address. error ERC20InvalidCallerAddress(address _caller); + /// @notice Unauthorized sender error from AccessControl. + error AccessControlUnauthorized(address _sender, address _account); + error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); + /// @notice Emitted when tokens are minted via a cross-chain bridge. /// @param _to The recipient of minted tokens. /// @param _amount The amount minted. /// @param _sender The bridge account that triggered the mint (msg.sender). - event CrosschainMint(address indexed _to, uint256 _amount, address indexed _sender); /// @notice Emitted when a crosschain transfer burns tokens. @@ -36,7 +38,17 @@ library LibERC20Bridgeable { /// @param _sender Address of the caller (msg.sender) who invoked crosschainBurn. event CrosschainBurn(address indexed _from, uint256 _amount, address indexed _sender); - /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 for storage location standardization + /// @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); + + /// ----------------------------------------------------------------------- + /// ERC20 integration (re-uses ERC20Facet storage layout) + /// ----------------------------------------------------------------------- + + /// @notice Storage slot for ERC-20 token using ERC8042 for storage location standardization /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); @@ -57,88 +69,104 @@ library LibERC20Bridgeable { * @dev Uses inline assembly to set the storage slot reference. * @return s The ERC20 storage struct reference. */ - function erc20Storage() internal pure returns (ERC20Storage storage s) { + function getERC20Storage() internal pure returns (ERC20Storage storage s) { bytes32 position = ERC20_STORAGE_POSITION; assembly { s.slot := position } } - /// @notice Storage slot for ERC-20 Bridgeable using ERC8042 as template - bytes32 constant BRIDGEABLE_STORAGE_POSITION = keccak256("compose.erc20.bridgeable"); + /// ----------------------------------------------------------------------- + /// AccessControl integration (re-uses AccessControlFacet storage layout) + /// ----------------------------------------------------------------------- - struct ERC20BridgeableStorage { - mapping(address => bool) trustedBridges; - } + /// @notice Storage slot identifier. + bytes32 constant ACCESS_STORAGE_POSITION = keccak256("compose.accesscontrol"); - function bridgeStorage() internal pure returns (ERC20BridgeableStorage storage s) { - bytes32 position = BRIDGEABLE_STORAGE_POSITION; + /// @notice storage struct for the AccessControl. + struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + } + /// @notice helper to return AccessControlStorage at its diamond slot + function getAccessControlStorage() internal pure returns (AccessControlStorage storage s) { + bytes32 position = ACCESS_STORAGE_POSITION; assembly { s.slot := position } } + /// @notice role identifier for trusted bridge actors - // the bridge account must be a trusted account - /// @notice Internal crosschain mint logic. MUST be called only after validating caller. - /// @dev Increases totalSupply and recipient balance and emits CrosschainMint. + bytes32 internal constant TRUSTED_BRIDGE_ROLE = keccak256("trusted-bridge"); + + /// @notice Cross-chain mint — callable only by an address having the `trusted-bridge` role. /// @param _account The account to mint tokens to. /// @param _value The amount to mint. function crosschainMint(address _account, uint256 _value) internal { - ERC20Storage storage erc20 = erc20Storage(); - ERC20BridgeableStorage storage bridge = bridgeStorage(); + ERC20Storage storage erc20 = getERC20Storage(); + + AccessControlStorage storage acs = getAccessControlStorage(); - if (bridge.trustedBridges[msg.sender] == false) { - revert ERC20InvalidBridgeAccount(msg.sender); + // authorize: caller must have the trusted-bridge role + if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { + revert AccessControlUnauthorized(msg.sender, _account); } + if (_account == address(0)) { revert ERC20InvalidReciever(address(0)); } + unchecked { erc20.totalSupply += _value; erc20.balanceOf[_account] += _value; } + emit Transfer(address(0), _account, _value); emit CrosschainMint(_account, _value, msg.sender); } - /// @notice Internal crosschain burn logic. MUST be called only after validating caller. - /// @dev Decreases totalSupply and the `from` balance and emits CrosschainBurn. + /// @notice Cross-chain burn — callable only by an address having the `trusted-bridge` role. /// @param _from The account to burn tokens from. /// @param _value The amount to burn. function crosschainBurn(address _from, uint256 _value) internal { - ERC20Storage storage erc20 = erc20Storage(); - ERC20BridgeableStorage storage bridge = bridgeStorage(); + ERC20Storage storage erc20 = getERC20Storage(); + + AccessControlStorage storage acs = getAccessControlStorage(); - if (bridge.trustedBridges[msg.sender] == false) { - revert ERC20InvalidBridgeAccount(msg.sender); + // authorize: caller must have the trusted-bridge role + if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { + revert AccessControlUnauthorized(msg.sender, _from); } + if (_from == address(0)) { revert ERC20InvalidReciever(address(0)); } + uint256 accountBalance = erc20.balanceOf[_from]; if (accountBalance < _value) { revert ERC20InsufficientBalance(_from, accountBalance, _value); } + unchecked { erc20.totalSupply -= _value; erc20.balanceOf[_from] -= _value; } - + emit Transfer(_from, address(0), _value); emit CrosschainBurn(_from, _value, msg.sender); } - /// @notice Internal check to check if the bridge is trusted. - /// @dev Reverts if caller is zero or not in the trusted bridges mapping. - /// @param _caller The address to validate + /// @notice Internal check to check if the bridge (caller) is trusted. + /// @dev Reverts if caller is zero or not in the AccessControl `trusted-bridge` role. + /// @param _caller The address to validate function checkTokenBridge(address _caller) internal view { - ERC20BridgeableStorage storage bridge = bridgeStorage(); + AccessControlStorage storage acs = getAccessControlStorage(); if (_caller == address(0)) { revert ERC20InvalidBridgeAccount(address(0)); } - if (bridge.trustedBridges[_caller] == false) { + + if (!acs.hasRole[_caller][TRUSTED_BRIDGE_ROLE]) { revert ERC20InvalidBridgeAccount(_caller); } } From f4c6128ab8d07b206877b269102bb31b41583be4 Mon Sep 17 00:00:00 2001 From: beebozy Date: Mon, 24 Nov 2025 11:50:34 -0800 Subject: [PATCH 7/8] code refactoring --- src/ERC20Bridgeable/ERC20BridgeableFacet.sol | 20 ++++++++++--------- .../libraries/LibERC20Bridgeable.sol | 16 ++++++++------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/ERC20Bridgeable/ERC20BridgeableFacet.sol b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol index d894585e..9cb3194d 100644 --- a/src/ERC20Bridgeable/ERC20BridgeableFacet.sol +++ b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol @@ -34,9 +34,12 @@ contract ERC20BridgeableFacet { /// @param _caller is the invalid address. error ERC20InvalidCallerAddress(address _caller); - /// @notice Unauthorized sender error from AccessControl. - error AccessControlUnauthorized(address _sender, address _account); + /// @notice Thrown when the account does not have a specific role. + /// @param _role The role that the account does not have. + /// @param _account The account that does not have the role. + error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); /// @notice Emitted when tokens are minted via a cross-chain bridge. @@ -143,9 +146,9 @@ contract ERC20BridgeableFacet { AccessControlStorage storage acs = getAccessControlStorage(); // authorize: caller must have the trusted-bridge role - if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { - revert AccessControlUnauthorized(msg.sender, _account); - } + if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { + revert AccessControlUnauthorizedAccount(msg.sender, TRUSTED_BRIDGE_ROLE); +} if (_account == address(0)) { revert ERC20InvalidReciever(address(0)); @@ -168,10 +171,9 @@ contract ERC20BridgeableFacet { AccessControlStorage storage acs = getAccessControlStorage(); // authorize: caller must have the trusted-bridge role - if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { - revert AccessControlUnauthorized(msg.sender, _from); - } - + if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { + revert AccessControlUnauthorizedAccount(msg.sender, TRUSTED_BRIDGE_ROLE); +} if (_from == address(0)) { revert ERC20InvalidReciever(address(0)); } diff --git a/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol index 0ce217a6..dae8335c 100644 --- a/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol +++ b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol @@ -21,9 +21,11 @@ library LibERC20Bridgeable { /// @param _caller is the invalid address. error ERC20InvalidCallerAddress(address _caller); - /// @notice Unauthorized sender error from AccessControl. - error AccessControlUnauthorized(address _sender, address _account); - + /// @notice Thrown when the account does not have a specific role. + /// @param _role The role that the account does not have. + /// @param _account The account that does not have the role. + error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); /// @notice Emitted when tokens are minted via a cross-chain bridge. @@ -109,8 +111,8 @@ library LibERC20Bridgeable { // authorize: caller must have the trusted-bridge role if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { - revert AccessControlUnauthorized(msg.sender, _account); - } + revert AccessControlUnauthorizedAccount(msg.sender, TRUSTED_BRIDGE_ROLE); +} if (_account == address(0)) { revert ERC20InvalidReciever(address(0)); @@ -135,8 +137,8 @@ library LibERC20Bridgeable { // authorize: caller must have the trusted-bridge role if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { - revert AccessControlUnauthorized(msg.sender, _from); - } + revert AccessControlUnauthorizedAccount(msg.sender, TRUSTED_BRIDGE_ROLE); +} if (_from == address(0)) { revert ERC20InvalidReciever(address(0)); From 0592367f699d93e932e92c169ed24ae03b8bc8a0 Mon Sep 17 00:00:00 2001 From: beebozy Date: Mon, 24 Nov 2025 07:15:33 -0800 Subject: [PATCH 8/8] refactoring of code --- src/ERC20Bridgeable/ERC20BridgeableFacet.sol | 81 +++---------------- .../libraries/LibERC20Bridgeable.sol | 26 +++--- 2 files changed, 21 insertions(+), 86 deletions(-) diff --git a/src/ERC20Bridgeable/ERC20BridgeableFacet.sol b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol index 9cb3194d..99109866 100644 --- a/src/ERC20Bridgeable/ERC20BridgeableFacet.sol +++ b/src/ERC20Bridgeable/ERC20BridgeableFacet.sol @@ -1,22 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; - -/// @title ERC-165 Standard Interface Detection Interface -/// @notice Interface for detecting what interfaces a contract implements -/// @dev ERC-165 allows contracts to publish their supported interfaces -interface IERC165 { - /// @notice Query if a contract implements an interface - /// @param _interfaceId The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `_interfaceId` and - /// `_interfaceId` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 _interfaceId) external view returns (bool); -} - /// @title ERC20Bridgeable — ERC-7802 Implementation Facet /// @notice Provides functions and storage layout for ERC20-Bridgeable token logic. /// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions + contract ERC20BridgeableFacet { /// @notice Revert when a provided receiver is invalid(e.g,zero address) . /// @param _receiver The invalid reciever address. @@ -34,12 +21,11 @@ contract ERC20BridgeableFacet { /// @param _caller is the invalid address. error ERC20InvalidCallerAddress(address _caller); - /// @notice Thrown when the account does not have a specific role. /// @param _role The role that the account does not have. /// @param _account The account that does not have the role. error AccessControlUnauthorizedAccount(address _account, bytes32 _role); - + error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); /// @notice Emitted when tokens are minted via a cross-chain bridge. @@ -60,28 +46,6 @@ contract ERC20BridgeableFacet { /// @param _value Amount of tokens transferred. event Transfer(address indexed _from, address indexed _to, uint256 _value); - /// ----------------------------------------------------------------------- - /// ERC165 integration (re-uses ERC165Facet storage layout) - /// ----------------------------------------------------------------------- - - /// @notice Storage slot for ERC-165 using ERC8042 for storage location standardization - /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. - bytes32 constant ERC165_STORAGE_POSITION = keccak256("compose.erc165"); - /// @notice ERC-165 storage layout using the ERC-8042 standard - /// @custom:storage-location erc8042:compose.erc165 - - struct ERC165Storage { - /// @notice Mapping of interface IDs to whether they are supported - mapping(bytes4 => bool) supportedInterfaces; - } - - function getErc165Storage() internal pure returns (ERC165Storage storage s) { - bytes32 position = ERC165_STORAGE_POSITION; - assembly { - s.slot := position - } - } - /// ----------------------------------------------------------------------- /// ERC20 integration (re-uses ERC20Facet storage layout) /// ----------------------------------------------------------------------- @@ -95,18 +59,15 @@ contract ERC20BridgeableFacet { * @custom:storage-location erc8042:compose.erc20 */ struct ERC20Storage { - string name; - string symbol; - uint8 decimals; - uint256 totalSupply; mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; } - /** * @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 getERC20Storage() internal pure returns (ERC20Storage storage s) { bytes32 position = ERC20_STORAGE_POSITION; assembly { @@ -134,9 +95,6 @@ contract ERC20BridgeableFacet { } } - /// @notice role identifier for trusted bridge actors - bytes32 internal constant TRUSTED_BRIDGE_ROLE = keccak256("trusted-bridge"); - /// @notice Cross-chain mint — callable only by an address having the `trusted-bridge` role. /// @param _account The account to mint tokens to. /// @param _value The amount to mint. @@ -146,9 +104,9 @@ contract ERC20BridgeableFacet { AccessControlStorage storage acs = getAccessControlStorage(); // authorize: caller must have the trusted-bridge role - if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { - revert AccessControlUnauthorizedAccount(msg.sender, TRUSTED_BRIDGE_ROLE); -} + if (!acs.hasRole[msg.sender]["trusted-bridge"]) { + revert AccessControlUnauthorizedAccount(msg.sender, "trusted-bridge"); + } if (_account == address(0)) { revert ERC20InvalidReciever(address(0)); @@ -171,9 +129,9 @@ contract ERC20BridgeableFacet { AccessControlStorage storage acs = getAccessControlStorage(); // authorize: caller must have the trusted-bridge role - if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { - revert AccessControlUnauthorizedAccount(msg.sender, TRUSTED_BRIDGE_ROLE); -} + if (!acs.hasRole[msg.sender]["trusted-bridge"]) { + revert AccessControlUnauthorizedAccount(msg.sender, "trusted-bridge"); + } if (_from == address(0)) { revert ERC20InvalidReciever(address(0)); } @@ -203,25 +161,8 @@ contract ERC20BridgeableFacet { revert ERC20InvalidBridgeAccount(address(0)); } - if (!acs.hasRole[_caller][TRUSTED_BRIDGE_ROLE]) { + if (!acs.hasRole[_caller]["trusted-bridge"]) { revert ERC20InvalidBridgeAccount(_caller); } } - - /// @notice Query if a contract implements an interface - /// @param _interfaceId The interface identifier, as specified in ERC-165 - /// @dev This function checks if the diamond supports the given interface ID - /// @return `true` if the contract implements `_interfaceId` and - /// `_interfaceId` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 _interfaceId) external view returns (bool) { - ERC165Storage storage erc165Storage = getErc165Storage(); - - // If the ERC165 interface itself is being queried, return true - // since this facet implements ERC165 - if (_interfaceId == type(IERC165).interfaceId) { - return true; - } - - return erc165Storage.supportedInterfaces[_interfaceId]; - } } diff --git a/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol index dae8335c..ef934e2a 100644 --- a/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol +++ b/src/ERC20Bridgeable/libraries/LibERC20Bridgeable.sol @@ -25,7 +25,7 @@ library LibERC20Bridgeable { /// @param _role The role that the account does not have. /// @param _account The account that does not have the role. error AccessControlUnauthorizedAccount(address _account, bytes32 _role); - + error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); /// @notice Emitted when tokens are minted via a cross-chain bridge. @@ -59,18 +59,15 @@ library LibERC20Bridgeable { * @custom:storage-location erc8042:compose.erc20 */ struct ERC20Storage { - string name; - string symbol; - uint8 decimals; - uint256 totalSupply; mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; } - /** * @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 getERC20Storage() internal pure returns (ERC20Storage storage s) { bytes32 position = ERC20_STORAGE_POSITION; assembly { @@ -97,9 +94,6 @@ library LibERC20Bridgeable { s.slot := position } } - /// @notice role identifier for trusted bridge actors - - bytes32 internal constant TRUSTED_BRIDGE_ROLE = keccak256("trusted-bridge"); /// @notice Cross-chain mint — callable only by an address having the `trusted-bridge` role. /// @param _account The account to mint tokens to. @@ -110,9 +104,9 @@ library LibERC20Bridgeable { AccessControlStorage storage acs = getAccessControlStorage(); // authorize: caller must have the trusted-bridge role - if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { - revert AccessControlUnauthorizedAccount(msg.sender, TRUSTED_BRIDGE_ROLE); -} + if (!acs.hasRole[msg.sender]["trusted-bridge"]) { + revert AccessControlUnauthorizedAccount(msg.sender, "trusted-bridge"); + } if (_account == address(0)) { revert ERC20InvalidReciever(address(0)); @@ -136,9 +130,9 @@ library LibERC20Bridgeable { AccessControlStorage storage acs = getAccessControlStorage(); // authorize: caller must have the trusted-bridge role - if (!acs.hasRole[msg.sender][TRUSTED_BRIDGE_ROLE]) { - revert AccessControlUnauthorizedAccount(msg.sender, TRUSTED_BRIDGE_ROLE); -} + if (!acs.hasRole[msg.sender]["trusted-bridge"]) { + revert AccessControlUnauthorizedAccount(msg.sender, "trusted-bridge"); + } if (_from == address(0)) { revert ERC20InvalidReciever(address(0)); @@ -168,7 +162,7 @@ library LibERC20Bridgeable { revert ERC20InvalidBridgeAccount(address(0)); } - if (!acs.hasRole[_caller][TRUSTED_BRIDGE_ROLE]) { + if (!acs.hasRole[_caller]["trusted-bridge"]) { revert ERC20InvalidBridgeAccount(_caller); } }