Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindings/bin/l2tokenregistry_deployed.hex

Large diffs are not rendered by default.

148 changes: 74 additions & 74 deletions bindings/bindings/l2tokenregistry.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/bindings/l2tokenregistry_more.go

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions contracts/contracts/l2/system/IL2TokenRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface IL2TokenRegistry {
/// @notice Token information structure
struct TokenInfo {
address tokenAddress; // ERC20 token contract address
bytes32 balanceSlot; // Token balance storage slot, bytes32(0) -> nil
bytes32 balanceSlot; // Token balance storage slot
bool isActive; // Whether the token is active
uint8 decimals; // Token decimals
uint256 scale; // Core convention: rateScaled = tokenScale * (tokenPrice / ethPrice) * 10^(ethDecimals - tokenDecimals)
Expand Down Expand Up @@ -62,6 +62,8 @@ interface IL2TokenRegistry {
error TokenNotFound();
error InvalidTokenID();
error InvalidTokenAddress();
error InvalidBalanceSlot();
error InvalidScale();
error InvalidPrice();
error InvalidPercent();
error CallerNotAllowed();
Expand Down Expand Up @@ -95,12 +97,14 @@ interface IL2TokenRegistry {
* @param _tokenIDs Array of token IDs
* @param _tokenAddresses Array of token addresses
* @param _balanceSlots Array of balance storage slots
* @param _needBalanceSlots Array of boolean flags indicating whether balanceSlot is needed
* @param _scales Array of scale values
*/
function registerTokens(
uint16[] memory _tokenIDs,
address[] memory _tokenAddresses,
bytes32[] memory _balanceSlots,
bool[] memory _needBalanceSlots,
uint256[] memory _scales
) external;

Expand All @@ -109,22 +113,31 @@ interface IL2TokenRegistry {
* @param _tokenID Token ID
* @param _tokenAddress Token contract address
* @param _balanceSlot Balance storage slot
* @param _needBalanceSlot Whether balanceSlot is needed (if false, stores 0; if true, stores balanceSlot+1)
* @param _scale Scale value
*/
function registerToken(uint16 _tokenID, address _tokenAddress, bytes32 _balanceSlot, uint256 _scale) external;
function registerToken(
uint16 _tokenID,
address _tokenAddress,
bytes32 _balanceSlot,
bool _needBalanceSlot,
uint256 _scale
) external;

/**
* @notice Update token information
* @param _tokenID Token ID
* @param _tokenAddress New token contract address
* @param _balanceSlot New balance storage slot
* @param _needBalanceSlot Whether balanceSlot is needed (if false, stores 0; if true, stores balanceSlot+1)
* @param _isActive Whether to activate
* @param _scale Scale value
*/
function updateTokenInfo(
uint16 _tokenID,
address _tokenAddress,
bytes32 _balanceSlot,
bool _needBalanceSlot,
bool _isActive,
uint256 _scale
) external;
Expand Down
109 changes: 84 additions & 25 deletions contracts/contracts/l2/system/L2TokenRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.UintSet;

/// @notice Mapping from tokenID to TokenInfo
mapping(uint16 => TokenInfo) public tokenRegistry;
mapping(uint16 tokenID => TokenInfo tokenInfo) public tokenRegistry;

/// @notice Mapping from token address to tokenID
mapping(address => uint16) public tokenRegistration;
mapping(address tokenAddress => uint16 tokenID) public tokenRegistration;

/// @notice Mapping from tokenID to price ratio (relative to ETH)
/// @dev priceRatio = tokenScale * (tokenPrice / ethPrice) * 10^(ethDecimals - tokenDecimals)
mapping(uint16 => uint256) public priceRatio;
mapping(uint16 tokenID => uint256 priceRatio) public priceRatio;

/// @notice Allow List whitelist
mapping(address => bool) public allowList;
mapping(address user => bool allowed) public allowList;

/// @notice Whether whitelist is enabled
bool public allowListEnabled = true;
Expand Down Expand Up @@ -65,6 +65,9 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
* @param owner_ Contract owner address
*/
function initialize(address owner_) external initializer {
__Ownable_init();
__ReentrancyGuard_init();

_transferOwnership(owner_);
allowListEnabled = true;
}
Expand Down Expand Up @@ -105,24 +108,27 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
* @param _tokenIDs Array of token IDs
* @param _tokenAddresses Array of token addresses
* @param _balanceSlots Array of balance storage slots
* @param _needBalanceSlots Array of boolean flags indicating whether balanceSlot is needed
* @param _scales Array of scale values
*/
function registerTokens(
uint16[] memory _tokenIDs,
address[] memory _tokenAddresses,
bytes32[] memory _balanceSlots,
bool[] memory _needBalanceSlots,
uint256[] memory _scales
) external onlyOwner {
) external onlyOwner nonReentrant {
if (
_tokenIDs.length != _tokenAddresses.length ||
_tokenIDs.length != _balanceSlots.length ||
_tokenIDs.length != _needBalanceSlots.length ||
_tokenIDs.length != _scales.length
) {
revert InvalidArrayLength();
}

for (uint256 i = 0; i < _tokenIDs.length; i++) {
_registerSingleToken(_tokenIDs[i], _tokenAddresses[i], _balanceSlots[i], _scales[i]);
_registerSingleToken(_tokenIDs[i], _tokenAddresses[i], _balanceSlots[i], _needBalanceSlots[i], _scales[i]);
}
}

Expand All @@ -131,15 +137,17 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
* @param _tokenID Token ID
* @param _tokenAddress Token contract address
* @param _balanceSlot Balance storage slot
* @param _needBalanceSlot Whether balanceSlot is needed (if false, stores 0; if true, stores balanceSlot+1)
* @param _scale Scale value
*/
function registerToken(
uint16 _tokenID,
address _tokenAddress,
bytes32 _balanceSlot,
bool _needBalanceSlot,
uint256 _scale
) external onlyOwner nonReentrant {
_registerSingleToken(_tokenID, _tokenAddress, _balanceSlot, _scale);
_registerSingleToken(_tokenID, _tokenAddress, _balanceSlot, _needBalanceSlot, _scale);
}

/**
Expand All @@ -158,13 +166,48 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
supportedTokenSet.remove(uint256(_tokenID));
}

/**
* @notice Internal function: Convert actual balanceSlot to stored value (adds 1 if needed)
* @param _actualSlot The actual balance slot value
* @param _needBalanceSlot Whether balanceSlot is needed
* @return The stored balance slot value (actualSlot + 1 if needed, otherwise 0)
*/
function _toStoredBalanceSlot(bytes32 _actualSlot, bool _needBalanceSlot) internal pure returns (bytes32) {
if (!_needBalanceSlot) {
return bytes32(0); // Don't store balanceSlot
}
if (_actualSlot == bytes32(type(uint256).max)) revert InvalidBalanceSlot();
bytes32 storedSlot;
assembly {
storedSlot := add(_actualSlot, 1)
}
return storedSlot;
}

/**
* @notice Internal function: Convert stored balanceSlot to actual value (subtracts 1 if non-zero)
* @param _storedSlot The stored balance slot value
* @return The actual balance slot value (storedSlot - 1 if non-zero, otherwise 0)
*/
function _toActualBalanceSlot(bytes32 _storedSlot) internal pure returns (bytes32) {
if (_storedSlot == bytes32(0)) {
return bytes32(0); // No balanceSlot stored
}
bytes32 actualSlot;
assembly {
actualSlot := sub(_storedSlot, 1)
}
return actualSlot;
}

/**
* @notice Internal function: Register a single token
*/
function _registerSingleToken(
uint16 _tokenID,
address _tokenAddress,
bytes32 _balanceSlot,
bool _needBalanceSlot,
uint256 _scale
) internal {
// Check token address
Expand All @@ -175,17 +218,22 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
if (tokenRegistry[_tokenID].tokenAddress != address(0)) revert TokenAlreadyRegistered();
if (tokenRegistration[_tokenAddress] != 0) revert TokenAlreadyRegistered();

// Validate scale is non-zero
if (_scale == 0) revert InvalidScale();

// Get decimals from contract
uint8 decimals = 18; // Default value
try IERC20Infos(_tokenAddress).decimals() returns (uint8 v) {
decimals = v;
} catch {
// If call fails, use default value 18
}

// Register token (isActive defaults to false)
// Note: balanceSlot is stored as actualSlot + 1 if needBalanceSlot is true, otherwise 0
tokenRegistry[_tokenID] = TokenInfo({
tokenAddress: _tokenAddress,
balanceSlot: _balanceSlot,
balanceSlot: _toStoredBalanceSlot(_balanceSlot, _needBalanceSlot),
isActive: false,
decimals: decimals,
scale: _scale
Expand All @@ -200,13 +248,15 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
* @param _tokenID Token ID
* @param _tokenAddress New token contract address
* @param _balanceSlot New balance storage slot
* @param _needBalanceSlot Whether balanceSlot is needed (if false, stores 0; if true, stores balanceSlot+1)
* @param _isActive Whether to activate
* @param _scale Scale value
*/
function updateTokenInfo(
uint16 _tokenID,
address _tokenAddress,
bytes32 _balanceSlot,
bool _needBalanceSlot,
bool _isActive,
uint256 _scale
) external onlyOwner nonReentrant {
Expand All @@ -228,10 +278,11 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
// If call fails, use default value 18
}
// Update registration information
// Note: balanceSlot is stored as actualSlot + 1 if needBalanceSlot is true, otherwise 0
address oldAddress = tokenRegistry[_tokenID].tokenAddress;
tokenRegistry[_tokenID] = TokenInfo({
tokenAddress: _tokenAddress,
balanceSlot: _balanceSlot,
balanceSlot: _toStoredBalanceSlot(_balanceSlot, _needBalanceSlot),
isActive: _isActive,
decimals: decimals,
scale: _scale
Expand All @@ -253,11 +304,14 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
* @notice Remove a token from registry
* @param _tokenID Token ID to remove
*/
function removeToken(uint16 _tokenID) external onlyOwner nonReentrant {
function removeToken(uint16 _tokenID) external onlyOwner {
// Check if token exists
address tokenAddress = tokenRegistry[_tokenID].tokenAddress;
if (tokenAddress == address(0)) revert TokenNotFound();

// Check if token is in supported list
if (!supportedTokenSet.contains(uint256(_tokenID))) revert TokenNotFound();

// Remove from mappings
delete tokenRegistry[_tokenID];
delete tokenRegistration[tokenAddress];
Expand Down Expand Up @@ -353,6 +407,7 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
* - Substituting ratio: tokenAmount = (ethAmount * 10^tokenDecimals) / (tokenScale * (tokenPrice / ethPrice) * 10^(18 - tokenDecimals))
* - Simplified: tokenAmount = (ethAmount * 10^tokenDecimals * 10^tokenDecimals) / (tokenScale * tokenPrice * 10^18 / ethPrice)
* - Final: tokenAmount = (ethAmount * ethPrice * 10^tokenDecimals) / (tokenScale * tokenPrice * 10^18)
* - Note: Uses ceiling division to ensure users receive fair token amounts
* @param _tokenID Token ID of the ERC20 token
* @param _ethAmount ETH amount (unit: wei)
* @return tokenAmount Corresponding token amount (unit: token's smallest unit)
Expand All @@ -371,10 +426,12 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
uint256 ratio = priceRatio[_tokenID];
if (ratio == 0) revert InvalidPrice();

// Calculate token amount:
// tokenAmount = (ethAmount * tokenScale) / ratio
// where ratio already contains tokenScale and decimals adjustment to eth (wei) and token smallest unit.
tokenAmount = (_ethAmount * uint256(info.scale)) / ratio;
// Calculate token amount with ceiling division:
// tokenAmount = ceil((ethAmount * tokenScale) / ratio)
// Using formula: ceil(a/b) = (a + b - 1) / b
uint256 numerator = _ethAmount * uint256(info.scale);
tokenAmount = (numerator + ratio - 1) / ratio;

if (tokenAmount == 0) revert InvalidPrice();

return tokenAmount;
Expand All @@ -383,11 +440,16 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
/**
* @notice Get token information
* @param _tokenID Token ID
* @return TokenInfo structure
* @return TokenInfo structure with actual balanceSlot (automatically -1 from stored value)
*/
function getTokenInfo(uint16 _tokenID) external view returns (TokenInfo memory) {
if (tokenRegistry[_tokenID].tokenAddress == address(0)) revert TokenNotFound();
return tokenRegistry[_tokenID];

TokenInfo memory info = tokenRegistry[_tokenID];
// Convert stored balanceSlot to actual value
info.balanceSlot = _toActualBalanceSlot(info.balanceSlot);

return info;
}

/**
Expand Down Expand Up @@ -416,7 +478,7 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
if (tokenRegistry[_tokenID].tokenAddress == address(0)) revert TokenNotFound();

// Validate scale is non-zero
if (_newScale == 0) revert InvalidPrice(); // or create a new error like InvalidScale
if (_newScale == 0) revert InvalidScale();
tokenRegistry[_tokenID].scale = _newScale;

emit TokenScaleUpdated(_tokenID, _newScale);
Expand Down Expand Up @@ -461,16 +523,13 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
function getSupportedTokenList() external view returns (TokenEntry[] memory) {
uint256[] memory values = supportedTokenSet.values();
TokenEntry[] memory tokenList = new TokenEntry[](values.length);

for (uint256 i = 0; i < values.length; ++i) {
uint16 tokenID = uint16(values[i]);
address tokenAddress = tokenRegistry[tokenID].tokenAddress;
tokenList[i] = TokenEntry({
tokenID: tokenID,
tokenAddress: tokenAddress
});
tokenList[i] = TokenEntry({tokenID: tokenID, tokenAddress: tokenAddress});
}

return tokenList;
}

Expand All @@ -481,11 +540,11 @@ contract L2TokenRegistry is IL2TokenRegistry, OwnableUpgradeable, ReentrancyGuar
function getSupportedIDList() external view returns (uint16[] memory) {
uint256[] memory values = supportedTokenSet.values();
uint16[] memory tokenIDs = new uint16[](values.length);

for (uint256 i = 0; i < values.length; ++i) {
tokenIDs[i] = uint16(values[i]);
}

return tokenIDs;
}

Expand Down
Loading
Loading