Created
November 9, 2023 14:01
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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