RebasingStakingToken

Description:

ERC20 token contract with Mintable, Burnable, Factory capabilities. Standard implementation for fungible tokens on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/pmETH.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.19;\r
\r
interface IERC20 {\r
    function totalSupply() external view returns (uint256);\r
    function balanceOf(address account) external view returns (uint256);\r
    function transfer(address recipient, uint256 amount) external returns (bool);\r
    function allowance(address owner, address spender) external view returns (uint256);\r
    function approve(address spender, uint256 amount) external returns (bool);\r
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\r
    \r
    event Transfer(address indexed from, address indexed to, uint256 value);\r
    event Approval(address indexed owner, address indexed spender, uint256 value);\r
}\r
\r
interface IERC20Metadata is IERC20 {\r
    function name() external view returns (string memory);\r
    function symbol() external view returns (string memory);\r
    function decimals() external view returns (uint8);\r
}\r
\r
interface IReth {\r
    function getExchangeRate() external view returns (uint256);\r
}\r
\r
contract RebasingStakingToken is IERC20Metadata {\r
    // ============ State Variables ============\r
    \r
    mapping(address => uint256) private _shares;\r
    mapping(address => mapping(address => uint256)) private _allowances;\r
    mapping(address => bool) private allowlist;\r
    mapping(address => uint256) private pending;\r
    \r
    uint256 private _totalShares;\r
    uint256 private lastRatio;\r
    uint256 private rebaseIndex;\r
    uint256 public multiplier;\r
\r
    uint256 public lastRebaseTime;\r
\r
    IReth rethToken;\r
    \r
    // Custodian management\r
    address public custodian;\r
    address public pendingCustodian;\r
    uint256 public custodianChangeDeadline;\r
    uint256 public constant CUSTODIAN_GRACE_PERIOD = 1 hours;\r
    uint256 public constant COOLDOWN_WINDOW = 1 days;\r
    \r
    // ERC20 Metadata\r
    string public override name;\r
    string public override symbol;\r
    uint8 public constant override decimals = 18;\r
    \r
    // Constants for calculations (all using 18 decimals)\r
    uint256 private constant WAD = 1e18;\r
    uint256 private constant SECONDS_PER_YEAR = 365 days;\r
\r
    // ============ Events ============\r
    \r
    event MultiplierUpdated(\r
        uint256 newMultiplier, \r
        uint256 time,\r
        address updatedBy\r
    );\r
    event WithdrawalRequsted(address indexed staker, uint256 amount, uint256 time);\r
    event Rebase(uint256 newIndex, uint256 secondsElapsed);\r
    event AllowlistUpdated(address indexed account, bool status);\r
    event CustodianChanged(address indexed oldCustodian, address indexed newCustodian);\r
    event CustodianChangeProposed(address indexed currentCustodian, address indexed proposedCustodian, uint256 deadline);\r
    event CustodianChangeCancelled(address indexed currentCustodian, address indexed cancelledCustodian);\r
    \r
    // ============ Modifiers ============\r
    \r
    modifier onlyCustodian() {\r
        require(msg.sender == custodian, "pmETH: caller is not the custodian");\r
        _;\r
    }\r
    \r
    modifier checkAllowlist(address from, address to) {\r
        require(\r
            allowlist[from] && allowlist[to],\r
            "pmETH: transfer not allowed - addresses not in allowlist"\r
        );\r
        _;\r
    }\r
\r
    // ============ Constructor ============  \r
    \r
    constructor(\r
        string memory _name,\r
        string memory _symbol,\r
        address _custodian,\r
        uint256 _initialSupply,\r
        uint256 _initialMultiplier,\r
        address _rethTokenAddress\r
    ) {\r
        require(_custodian != address(0), "pmETH: custodian is zero address");\r
        \r
        name = _name;\r
        symbol = _symbol;\r
        custodian = _custodian;\r
        rebaseIndex = WAD;\r
        lastRebaseTime = block.timestamp;\r
        multiplier = _initialMultiplier;\r
        require(_rethTokenAddress != address(0), "Invalid address");\r
        rethToken = IReth(_rethTokenAddress);\r
        lastRatio = rethToken.getExchangeRate();\r
        \r
        if (_initialSupply > 0) {\r
            _totalShares = _initialSupply;\r
            _shares[msg.sender] = _initialSupply;\r
            emit Transfer(address(0), msg.sender, _initialSupply);\r
        }\r
\r
        allowlist[msg.sender] = true;\r
        allowlist[_custodian] = true;\r
        emit AllowlistUpdated(msg.sender, true);\r
        emit AllowlistUpdated(_custodian, true);\r
    }\r
    \r
    // ============ Optimized Math Functions ============\r
    \r
    function fastWadPow(uint256 a, uint256 b, uint256 c, uint256 d) public pure returns (uint256 result) {\r
        assembly {\r
            let wadValue := 0xde0b6b3a7640000 // 1e18\r
\r
            if iszero(c) { revert(0, 0) }\r
\r
            let ratio := div(mul(b, wadValue), c)\r
            if lt(ratio, wadValue) { revert(0, 0) }\r
            let epsilon := sub(ratio, wadValue)\r
\r
            let d_minus_1 := sub(d, wadValue)\r
            let tmp := mul(epsilon, d_minus_1)         // eps * (d-1)\r
            tmp := div(tmp, wadValue)                  // scale down\r
            tmp := div(tmp, 2)                         // divide by 2\r
            tmp := add(tmp, wadValue)                  // 1 + ...\r
\r
            let d_eps_scaled := div(mul(d, epsilon), wadValue) // (d * eps) / WAD\r
            let res := div(mul(d_eps_scaled, tmp), wadValue) // ((d * eps)/WAD * tmp)/WAD\r
            let powerVal := add(wadValue, res)\r
\r
            result := div(mul(a, powerVal), wadValue)\r
        }\r
    }\r
\r
    // ============ Rebase Function ============\r
\r
    function rebase() public {\r
        if (block.timestamp > lastRebaseTime + COOLDOWN_WINDOW) {\r
            uint256 _newRatio = rethToken.getExchangeRate();\r
            if (_newRatio > lastRatio) {\r
                uint256 timeElapsed = block.timestamp - lastRebaseTime;\r
            \r
                rebaseIndex = fastWadPow(rebaseIndex, _newRatio, lastRatio, multiplier);\r
                lastRebaseTime = block.timestamp;\r
                lastRatio = _newRatio;\r
\r
                emit Rebase(rebaseIndex, timeElapsed);\r
            }\r
        }\r
    }\r
\r
    // ============ Internal Functions ============\r
\r
    function _transfer(\r
        address sender,\r
        address recipient,\r
        uint256 amount\r
    ) internal checkAllowlist(sender, recipient) {\r
        require(sender != address(0), "ERC20: transfer from the zero address");\r
        require(recipient != address(0), "ERC20: transfer to the zero address");\r
        \r
        rebase();\r
        \r
        uint256 senderBalance = (_shares[sender] * rebaseIndex) / WAD;\r
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");\r
        \r
        uint256 sharesToTransfer = tokensToShares(amount);\r
        require(sharesToTransfer > 0, "pmETH: cannot transfer 0 shares");\r
        \r
        _shares[sender] -= sharesToTransfer;\r
        _shares[recipient] += sharesToTransfer;\r
        \r
        emit Transfer(sender, recipient, amount);\r
    }\r
    \r
    function _approve(\r
        address owner,\r
        address spender,\r
        uint256 amount\r
    ) internal {\r
        require(owner != address(0), "ERC20: approve from the zero address");\r
        require(spender != address(0), "ERC20: approve to the zero address");\r
        \r
        _allowances[owner][spender] = amount;\r
        emit Approval(owner, spender, amount);\r
    }\r
\r
    function getCurrentRebaseIndex() private view returns (uint256) {\r
        uint256 _currentRebaseIndex = rebaseIndex;\r
\r
        uint256 _newRatio = rethToken.getExchangeRate();\r
        if (_newRatio > lastRatio) {\r
            _currentRebaseIndex = fastWadPow(rebaseIndex, _newRatio, lastRatio, multiplier);\r
        }\r
\r
        return _currentRebaseIndex;\r
    }\r
\r
    function tokensToShares(uint256 amount) public view returns (uint256) {\r
        return (amount * WAD) / rebaseIndex;\r
    }\r
    \r
    function sharesToTokens(uint256 sharesAmount) public view returns (uint256) {\r
        return (sharesAmount * rebaseIndex) / WAD;\r
    }\r
\r
    // ============ ERC20 Functions ============\r
    \r
    function totalSupply() public view override returns (uint256) {\r
        return (_totalShares * getCurrentRebaseIndex()) / WAD;\r
    }\r
\r
    function balanceOf(address account) public view override returns (uint256) {\r
        if (_shares[account] == 0) return 0;\r
        \r
        return (_shares[account] * getCurrentRebaseIndex()) / WAD;\r
    }\r
    \r
    function transfer(address recipient, uint256 amount) public override returns (bool) {\r
        _transfer(msg.sender, recipient, amount);\r
        return true;\r
    }\r
    \r
    function allowance(address owner, address spender) public view override returns (uint256) {\r
        return _allowances[owner][spender];\r
    }\r
    \r
    function approve(address spender, uint256 amount) public override returns (bool) {\r
        _approve(msg.sender, spender, amount);\r
        return true;\r
    }\r
    \r
    function transferFrom(\r
        address sender,\r
        address recipient,\r
        uint256 amount\r
    ) public override returns (bool) {\r
        uint256 currentAllowance = _allowances[sender][msg.sender];\r
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");\r
        \r
        _transfer(sender, recipient, amount);\r
        \r
        if (currentAllowance != type(uint256).max) {\r
            _approve(sender, msg.sender, currentAllowance - amount);\r
        }\r
        \r
        return true;\r
    }\r
\r
    // ============ Deposit/Withdraw Functions ============\r
\r
    function deposit() external payable {\r
        require(allowlist[msg.sender], "pmETH: deposit not allowed - address not in allowlist");\r
        require(msg.value > 0, "pmETH: cannot deposit 0 ether");\r
\r
        rebase();\r
\r
        uint256 sharesToDeposit = tokensToShares(msg.value);\r
        _shares[msg.sender] += sharesToDeposit;\r
        _totalShares += sharesToDeposit;\r
\r
        (bool success, ) = payable(custodian).call{value: msg.value}("");\r
        require(success, "Failed to send Ether");\r
\r
        emit Transfer(address(0), msg.sender, msg.value);\r
    }\r
\r
    function depositOnBehalf(address _beneficiary) external payable {\r
        require(allowlist[_beneficiary], "pmETH: deposit not allowed - beneficiary address not in allowlist");\r
        require(msg.value > 0, "pmETH: cannot deposit 0 ether");\r
\r
        rebase();\r
\r
        uint256 sharesToDeposit = tokensToShares(msg.value);\r
        _shares[_beneficiary] += sharesToDeposit;\r
        _totalShares += sharesToDeposit;\r
\r
        (bool success, ) = payable(custodian).call{value: msg.value}("");\r
        require(success, "Failed to send Ether");\r
        \r
        emit Transfer(address(0), _beneficiary, msg.value);\r
    }\r
\r
    function requestWithdrawal(uint256 _amount) external {\r
        require(allowlist[msg.sender], "pmETH: withdrawal not allowed - address not in allowlist");\r
        require(_amount > 0, "pmETH: cannot withdraw 0 ether");\r
\r
        rebase();\r
\r
        require(sharesToTokens(_shares[msg.sender]) - pending[msg.sender] >= _amount, "pmETH: insufficient balance to withdraw");\r
\r
        pending[msg.sender] += _amount;\r
\r
        emit WithdrawalRequsted(msg.sender, _amount, block.timestamp);\r
    }\r
\r
    function fulfillWithdrawal(address _address) external payable onlyCustodian {\r
        require(msg.value > 0, "pmETH: cannot redeem 0 ether");\r
        require(pending[_address] > 0, "pmETH: no pending withdrawal for this address");\r
        require(msg.value <= pending[_address], "pmETH: cannot fulfill more than requested");\r
\r
        rebase();\r
\r
        pending[_address] -= msg.value;\r
        uint256 sharesToRedeem = tokensToShares(msg.value);\r
        _shares[_address] -= sharesToRedeem;\r
        _totalShares -= sharesToRedeem;\r
\r
        (bool success, ) = payable(_address).call{value: msg.value}("");\r
        require(success, "Failed to send Ether");\r
\r
        emit Transfer(_address, address(0), msg.value);\r
    }\r
\r
    // ============ Custodian Mint/Burn Functions ============\r
    \r
    function mint(address account, uint256 amount) external onlyCustodian {\r
        require(account != address(0), "pmETH: mint to zero address");\r
        require(amount > 0, "pmETH: cannot mint 0 tokens");\r
        \r
        rebase();\r
        \r
        uint256 sharesToMint = tokensToShares(amount);\r
        _totalShares += sharesToMint;\r
        _shares[account] += sharesToMint;\r
        \r
        emit Transfer(address(0), account, amount);\r
    }\r
    \r
    function burn(address account, uint256 amount) external onlyCustodian {\r
        require(account != address(0), "pmETH: burn from zero address");\r
        \r
        rebase();\r
        \r
        uint256 accountBalance = balanceOf(account);\r
        require(accountBalance >= amount, "pmETH: burn amount exceeds balance");\r
        \r
        uint256 sharesToBurn = tokensToShares(amount);\r
        _shares[account] -= sharesToBurn;\r
        _totalShares -= sharesToBurn;\r
        \r
        emit Transfer(account, address(0), amount);\r
    }\r
\r
    // ============ Other Custodian Functions ============\r
\r
    function setRethAddress(address _newAddress) external onlyCustodian {\r
        require(_newAddress != address(0), "Invalid address");\r
        rebase();\r
\r
        rethToken = IReth(_newAddress);\r
        lastRatio = rethToken.getExchangeRate();\r
    }\r
    \r
    function setMultiplier(uint256 _newMultiplier) external onlyCustodian {\r
        require(5*1e17 < _newMultiplier && _newMultiplier < 2*1e18, "pmETH: Multiplier out of range");\r
        rebase();\r
        multiplier = _newMultiplier;        \r
        emit MultiplierUpdated(_newMultiplier, block.timestamp, msg.sender);\r
    }\r
\r
    function setAllowlist(address account, bool status) external onlyCustodian {\r
        require(account != address(0), "pmETH: cannot allowlist zero address");\r
        allowlist[account] = status;\r
        emit AllowlistUpdated(account, status);\r
    }\r
\r
    function setAllowlistBatch(address[] calldata accounts, bool status) external onlyCustodian {\r
        for (uint256 i = 0; i < accounts.length; i++) {\r
            require(accounts[i] != address(0), "pmETH: cannot allowlist zero address");\r
            allowlist[accounts[i]] = status;\r
            emit AllowlistUpdated(accounts[i], status);\r
        }\r
    }\r
    \r
    function proposeCustodianChange(address newCustodian) external onlyCustodian {\r
        require(newCustodian != address(0), "pmETH: new custodian is zero address");\r
        require(newCustodian != custodian, "pmETH: new custodian same as current");\r
        require(newCustodian != pendingCustodian, "pmETH: custodian already proposed");\r
        \r
        pendingCustodian = newCustodian;\r
        custodianChangeDeadline = block.timestamp + CUSTODIAN_GRACE_PERIOD;\r
        \r
        emit CustodianChangeProposed(custodian, newCustodian, custodianChangeDeadline);\r
    }\r
    \r
    function acceptCustodianship() external {\r
        require(msg.sender == pendingCustodian, "pmETH: caller is not pending custodian");\r
        require(pendingCustodian != address(0), "pmETH: no pending custodian");\r
        require(block.timestamp <= custodianChangeDeadline, "pmETH: acceptance period expired");\r
        \r
        address oldCustodian = custodian;\r
        custodian = pendingCustodian;\r
        pendingCustodian = address(0);\r
        custodianChangeDeadline = 0;\r
\r
        allowlist[msg.sender] = true;\r
\r
        emit AllowlistUpdated(msg.sender, true);\r
        emit CustodianChanged(oldCustodian, custodian);\r
    }\r
\r
    function cancelCustodianChange() external onlyCustodian {\r
        require(pendingCustodian != address(0), "pmETH: no pending custodian change");\r
        \r
        address cancelledCustodian = pendingCustodian;\r
        pendingCustodian = address(0);\r
        custodianChangeDeadline = 0;\r
        \r
        emit CustodianChangeCancelled(custodian, cancelledCustodian);\r
    }\r
    \r
    function clearExpiredCustodianChange() external {\r
        require(pendingCustodian != address(0), "pmETH: no pending custodian change");\r
        require(block.timestamp > custodianChangeDeadline, "pmETH: deadline not yet passed");\r
        \r
        address expiredCustodian = pendingCustodian;\r
        pendingCustodian = address(0);\r
        custodianChangeDeadline = 0;\r
        \r
        emit CustodianChangeCancelled(custodian, expiredCustodian);\r
    }\r
    \r
    // ============ Additional View Functions ============\r
    \r
    function sharesOf(address account) public view returns (uint256) {\r
        return _shares[account];\r
    }\r
\r
    function pendingOf(address account) public view returns (uint256) {\r
        return pending[account];\r
    }\r
    \r
    function totalShares() public view returns (uint256) {\r
        return _totalShares;\r
    }\r
    \r
    function getRebaseIndex() public view returns (uint256) {\r
        return rebaseIndex;\r
    }\r
\r
    function isAllowlisted(address account) external view returns (bool) {\r
        return allowlist[account];\r
    }\r
\r
    function isTransferAllowed(address from, address to) external view returns (bool) {\r
        return allowlist[from] && allowlist[to];\r
    }\r
    \r
    function custodianChangeTimeRemaining() external view returns (uint256) {\r
        if (pendingCustodian == address(0) || block.timestamp > custodianChangeDeadline) {\r
            return 0;\r
        }\r
        return custodianChangeDeadline - block.timestamp;\r
    }\r
\r
    function hasPendingCustodianChange() external view returns (bool) {\r
        return pendingCustodian != address(0) && block.timestamp <= custodianChangeDeadline;\r
    }\r
}"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "remappings": []
  }
}}

Tags:
ERC20, Token, Mintable, Burnable, Factory|addr:0x1a0f36f69f0ace2084c6d60ba97a8ee9e844d6c4|verified:true|block:23698374|tx:0xa69a9d21087ebbc1c147de7c92554549e561d126f281a6a1cac3a85683bc0aed|first_check:1761929834

Submitted on: 2025-10-31 17:57:14

Comments

Log in to comment.

No comments yet.