Skip to content

Instantly share code, notes, and snippets.

@radeksvarz
Created November 9, 2023 14:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save radeksvarz/db527d4f0f077c20ec8fb570ef439bae to your computer and use it in GitHub Desktop.
Save radeksvarz/db527d4f0f077c20ec8fb570ef439bae to your computer and use it in GitHub Desktop.
xERC20 upgradeable version in line with OZ 4. Note setBridgeLimits instead of setLimits.
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.4 <0.9.0;
interface IXERC20 {
/**
* @notice Emits when a lockbox is set
*
* @param _lockbox The address of the lockbox
*/
event LockboxSet(address _lockbox);
/**
* @notice Emits when a limit is set
*
* @param _mintingLimit The updated minting limit we are setting to the bridge
* @param _burningLimit The updated burning limit we are setting to the bridge
* @param _bridge The address of the bridge we are setting the limit too
*/
event BridgeLimitsSet(uint256 _mintingLimit, uint256 _burningLimit, address indexed _bridge);
/**
* @notice Reverts when a user with too low of a limit tries to call mint/burn
*/
error IXERC20_NotHighEnoughLimits();
/**
* @notice Reverts when caller is not the factory
*/
error IXERC20_NotFactory();
struct Bridge {
BridgeParameters minterParams;
BridgeParameters burnerParams;
}
struct BridgeParameters {
uint256 timestamp;
uint256 ratePerSecond;
uint256 maxLimit;
uint256 currentLimit;
}
/**
* @notice Sets the lockbox address
*
* @param _lockbox The address of the lockbox
*/
function setLockbox(address _lockbox) external;
/**
* @notice Updates the limits of any bridge
* @dev Can only be called by the owner
* @param _mintingLimit The updated minting limit we are setting to the bridge
* @param _burningLimit The updated burning limit we are setting to the bridge
* @param _bridge The address of the bridge we are setting the limits too
*/
// TBD EIP whether setLimits or setBridgeLimits
// function setLimits(address _bridge, uint256 _mintingLimit, uint256 _burningLimit) external;
/**
* @notice Updates the limits of any bridge
* @dev Can only be called by the owner
* @param _mintingLimit The updated minting limit we are setting to the bridge
* @param _burningLimit The updated burning limit we are setting to the bridge
* @param _bridge The address of the bridge we are setting the limits too
*/
function setBridgeLimits(address _bridge, uint256 _mintingLimit, uint256 _burningLimit) external;
/**
* @notice Returns the max limit of a minter
*
* @param _bridge The bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function mintingMaxLimitOf(address _bridge) external view returns (uint256 _limit);
/**
* @notice Returns the max limit of a bridge
*
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function burningMaxLimitOf(address _bridge) external view returns (uint256 _limit);
/**
* @notice Returns the current limit of a minter
*
* @param _bridge The bridge we are viewing the limits of
* @return _limit The limit the minter has
*/
function mintingCurrentLimitOf(address _bridge) external view returns (uint256 _limit);
/**
* @notice Returns the current limit of a bridge
*
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function burningCurrentLimitOf(address _bridge) external view returns (uint256 _limit);
/**
* @notice Mints tokens for a user
* @dev Can only be called by a bridge
* @param _user The address of the user who needs tokens minted
* @param _amount The amount of tokens being minted
*/
function mint(address _user, uint256 _amount) external;
/**
* @notice Burns tokens for a user
* @dev Can only be called by a bridge
* @param _user The address of the user who needs tokens burned
* @param _amount The amount of tokens being burned
*/
function burn(address _user, uint256 _amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {Initializable} from "@oz-upgradeable/proxy/utils/Initializable.sol";
import {ERC20Upgradeable} from "@oz-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {IXERC20} from "./interfaces/IXERC20.sol";
abstract contract XERC20Upgradeable is Initializable, ERC20Upgradeable, IXERC20 {
/**
* @notice The duration it takes for the limits to fully replenish
*/
uint256 private constant _DURATION = 1 days;
/**
* @notice The address of the lockbox contract
*/
address public lockbox;
/**
* @notice Maps bridge address to bridge configurations
*/
mapping(address => Bridge) public bridges;
/// @dev Initializes the contract ...
function __XERC20_init() internal onlyInitializing {
__XERC20_init_unchained();
}
/// @dev Initializes the contract ...
function __XERC20_init_unchained() internal onlyInitializing {}
/**
* @notice Returns the max limit of a bridge
*
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function mintingMaxLimitOf(address _bridge) public view virtual returns (uint256 _limit) {
_limit = bridges[_bridge].minterParams.maxLimit;
}
/**
* @notice Returns the max limit of a bridge
*
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function burningMaxLimitOf(address _bridge) public view virtual returns (uint256 _limit) {
_limit = bridges[_bridge].burnerParams.maxLimit;
}
/**
* @notice Returns the current limit of a bridge
*
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function mintingCurrentLimitOf(address _bridge) public view virtual returns (uint256 _limit) {
_limit = _getCurrentLimit(
bridges[_bridge].minterParams.currentLimit,
bridges[_bridge].minterParams.maxLimit,
bridges[_bridge].minterParams.timestamp,
bridges[_bridge].minterParams.ratePerSecond
);
}
/**
* @notice Returns the current limit of a bridge
*
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function burningCurrentLimitOf(address _bridge) public view virtual returns (uint256 _limit) {
_limit = _getCurrentLimit(
bridges[_bridge].burnerParams.currentLimit,
bridges[_bridge].burnerParams.maxLimit,
bridges[_bridge].burnerParams.timestamp,
bridges[_bridge].burnerParams.ratePerSecond
);
}
/**
* @notice Uses the limit of any bridge
* @param _bridge The address of the bridge who is being changed
* @param _change The change in the limit
*/
function _useMinterLimits(address _bridge, uint256 _change) internal virtual {
uint256 _currentLimit = mintingCurrentLimitOf(_bridge);
bridges[_bridge].minterParams.timestamp = block.timestamp;
bridges[_bridge].minterParams.currentLimit = _currentLimit - _change;
}
/**
* @notice Uses the limit of any bridge
* @param _bridge The address of the bridge who is being changed
* @param _change The change in the limit
*/
function _useBurnerLimits(address _bridge, uint256 _change) internal virtual {
uint256 _currentLimit = burningCurrentLimitOf(_bridge);
bridges[_bridge].burnerParams.timestamp = block.timestamp;
bridges[_bridge].burnerParams.currentLimit = _currentLimit - _change;
}
/**
* @notice Updates the limit of any bridge
* @dev Can only be called by the owner
* @param _bridge The address of the bridge we are setting the limit too
* @param _limit The updated limit we are setting to the bridge
*/
function _changeMinterLimit(address _bridge, uint256 _limit) internal virtual {
uint256 _oldLimit = bridges[_bridge].minterParams.maxLimit;
uint256 _currentLimit = mintingCurrentLimitOf(_bridge);
bridges[_bridge].minterParams.maxLimit = _limit;
bridges[_bridge].minterParams.currentLimit = _calculateNewCurrentLimit(_limit, _oldLimit, _currentLimit);
bridges[_bridge].minterParams.ratePerSecond = _limit / _DURATION;
bridges[_bridge].minterParams.timestamp = block.timestamp;
}
/**
* @notice Updates the limit of any bridge
* @dev Can only be called by the owner
* @param _bridge The address of the bridge we are setting the limit too
* @param _limit The updated limit we are setting to the bridge
*/
function _changeBurnerLimit(address _bridge, uint256 _limit) internal virtual {
uint256 _oldLimit = bridges[_bridge].burnerParams.maxLimit;
uint256 _currentLimit = burningCurrentLimitOf(_bridge);
bridges[_bridge].burnerParams.maxLimit = _limit;
bridges[_bridge].burnerParams.currentLimit = _calculateNewCurrentLimit(_limit, _oldLimit, _currentLimit);
bridges[_bridge].burnerParams.ratePerSecond = _limit / _DURATION;
bridges[_bridge].burnerParams.timestamp = block.timestamp;
}
/**
* @notice Calculates the new current limit
* @param _limit The new limit
* @param _oldLimit The old limit
* @param _currentLimit The current limit
*/
function _calculateNewCurrentLimit(
uint256 _limit,
uint256 _oldLimit,
uint256 _currentLimit
) internal pure virtual returns (uint256 _newCurrentLimit) {
uint256 _difference;
if (_oldLimit > _limit) {
_difference = _oldLimit - _limit;
_newCurrentLimit = _currentLimit > _difference ? _currentLimit - _difference : 0;
} else {
_difference = _limit - _oldLimit;
_newCurrentLimit = _currentLimit + _difference;
}
}
/**
* @notice Gets the current limit
* @param _currentLimit The current limit
* @param _maxLimit The max limit
* @param _timestamp The timestamp of the last update
* @param _ratePerSecond The rate per second
*/
function _getCurrentLimit(
uint256 _currentLimit,
uint256 _maxLimit,
uint256 _timestamp,
uint256 _ratePerSecond
) internal view virtual returns (uint256 _limit) {
_limit = _currentLimit;
if (_limit == _maxLimit) {
return _limit;
} else if (_timestamp + _DURATION <= block.timestamp) {
_limit = _maxLimit;
} else if (_timestamp + _DURATION > block.timestamp) {
uint256 _timePassed = block.timestamp - _timestamp;
uint256 _calculatedLimit = _limit + (_timePassed * _ratePerSecond);
_limit = _calculatedLimit > _maxLimit ? _maxLimit : _calculatedLimit;
}
}
/**
* @notice Internal function for burning tokens
*
* @param _caller The caller address
* @param _user The user address
* @param _amount The amount to burn
*/
function _burnWithCaller(address _caller, address _user, uint256 _amount) internal virtual {
if (_caller != lockbox) {
uint256 _currentLimit = burningCurrentLimitOf(_caller);
if (_currentLimit < _amount) revert IXERC20_NotHighEnoughLimits();
_useBurnerLimits(_caller, _amount);
}
_burn(_user, _amount);
}
/**
* @notice Internal function for minting tokens
*
* @param _caller The caller address
* @param _user The user address
* @param _amount The amount to mint
*/
function _mintWithCaller(address _caller, address _user, uint256 _amount) internal virtual {
if (_caller != lockbox) {
uint256 _currentLimit = mintingCurrentLimitOf(_caller);
if (_currentLimit < _amount) revert IXERC20_NotHighEnoughLimits();
_useMinterLimits(_caller, _amount);
}
_mint(_user, _amount);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[0xc64] private __gap;
}
//
// example usage of upgradable xERC20
//
pragma solidity 0.8.19;
import "@oz-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {Initializable} from "@oz-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@oz-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {XERC20Upgradeable} from "./XERC20Upgradeable.sol";
...
contract XToken is
Initializable,
ERC20Upgradeable,
XERC20Upgradeable,
UUPSUpgradeable
...
{
bytes32 public constant BRIDGE_ADMIN_ROLE = keccak256("BRIDGE_ADMIN_ROLE");
constructor() {
_disableInitializers();
}
//
// BRIDGE management
//
/**
* @notice Sets the EIP 7281 lockbox address
*
* @param _lockbox The address of the lockbox
*/
function setLockbox(address _lockbox) public virtual onlyRole(BRIDGE_ADMIN_ROLE) {
emit LockboxSet(_lockbox);
lockbox = _lockbox;
}
/**
* @notice Updates the limits of any bridge
* @dev Can only be called by the BRIDGE_ADMIN_ROLE
* @param _mintingLimit The updated minting limit we are setting to the bridge
* @param _burningLimit The updated burning limit we are setting to the bridge
* @param _bridge The address of the bridge we are setting the limits too
*/
function setBridgeLimits(
address _bridge,
uint256 _mintingLimit,
uint256 _burningLimit
) external virtual onlyRole(BRIDGE_ADMIN_ROLE) {
emit BridgeLimitsSet(_mintingLimit, _burningLimit, _bridge);
_changeMinterLimit(_bridge, _mintingLimit);
_changeBurnerLimit(_bridge, _burningLimit);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment