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": []
}
}}
Submitted on: 2025-10-31 17:57:14
Comments
Log in to comment.
No comments yet.