Created
July 30, 2025 12:20
-
-
Save masihtehrani/293a850acb8f79d62e24f2018799f97c to your computer and use it in GitHub Desktop.
Revisions
-
masihtehrani created this gist
Jul 30, 2025 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,208 @@ // File: RwaToken.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; // All necessary imports import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; // This struct definition remains the same struct RwaTokenParams { string name; string symbol; uint256 initialSupply; address burnWallet; address batchWallet; address airdropWallet; address supportWallet; string tokenImageUrl; string websiteUrl; } // CORRECTED: Removed ERC20Upgradeable from the inheritance list as it's already included in ERC20PermitUpgradeable contract RwaToken is Initializable, AccessControlUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable, ERC20PermitUpgradeable { using SafeERC20 for IERC20; // --- Roles --- bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); bytes32 public constant FREEZER_ROLE = keccak256("FREEZER_ROLE"); bytes32 public constant DISTRIBUTOR_ROLE = keccak256("DISTRIBUTOR_ROLE"); bytes32 public constant RESCUER_ROLE = keccak256("RESCUER_ROLE"); bytes32 public constant CONFIGURATOR_ROLE = keccak256("CONFIGURATOR_ROLE"); // --- State Variables --- address public burnWallet; address public batchWallet; address public airdropWallet; address public supportWallet; string private _tokenImageUrl; string private _websiteUrl; mapping(address => bool) public frozenWallets; // --- Constants --- uint256 public constant DISTRIBUTION_LIMIT = 200; // --- Events --- event TokensBurned(address indexed from, uint256 amount); event WalletFrozen(address indexed wallet); event WalletUnfrozen(address indexed wallet); event BatchWalletFunded(uint256 amount); event AirdropWalletFunded(uint256 amount); event TokensDistributed(address indexed fromWallet, uint256 totalAmount, uint256 userCount); event TokenImageUrlUpdated(string newUrl); event WebsiteUrlUpdated(string newUrl); event TokensRescued(address indexed token, address indexed to, uint256 amount); event SupportWalletUpdated(address indexed newWallet); function initialize(RwaTokenParams calldata params) public initializer { __ERC20_init(params.name, params.symbol); __ERC20Permit_init(params.name); __AccessControl_init(); __Pausable_init(); __ReentrancyGuard_init(); require(params.burnWallet != address(0), "RwaToken: Burn wallet cannot be zero"); require(params.batchWallet != address(0), "RwaToken: Batch wallet cannot be zero"); require(params.airdropWallet != address(0), "RwaToken: Airdrop wallet cannot be zero"); require(params.supportWallet != address(0), "RwaToken: Support wallet cannot be zero"); burnWallet = params.burnWallet; batchWallet = params.batchWallet; airdropWallet = params.airdropWallet; supportWallet = params.supportWallet; _tokenImageUrl = params.tokenImageUrl; _websiteUrl = params.websiteUrl; address deployer = _msgSender(); _grantRole(DEFAULT_ADMIN_ROLE, deployer); _grantRole(PAUSER_ROLE, deployer); _grantRole(FREEZER_ROLE, deployer); _grantRole(DISTRIBUTOR_ROLE, deployer); _grantRole(RESCUER_ROLE, deployer); _grantRole(CONFIGURATOR_ROLE, deployer); _mint(deployer, params.initialSupply); } modifier whenNotFrozen(address account) { require(!frozenWallets[account], "RwaToken: Wallet is frozen"); _; } /** * @dev Central hook for all transfer-related checks. */ // CORRECTED: Simplified override. The compiler now understands which function to override. function _update(address from, address to, uint256 value) internal virtual override { require(!paused(), "RwaToken: token transfer while paused"); if (from == address(0) || to == address(0)) { super._update(from, to, value); return; } require(from != to, "RwaToken: Self-transfer is not allowed"); require(!frozenWallets[from], "RwaToken: Sender wallet is frozen"); require(!frozenWallets[to], "RwaToken: Receiver wallet is frozen"); if (from == supportWallet || to == supportWallet) { super._update(from, to, value); return; } require(!hasRole(DISTRIBUTOR_ROLE, from), "RwaToken: Distributors must use funding functions"); require(to != burnWallet, "RwaToken: Use burn() instead of direct transfer"); require(to != batchWallet, "RwaToken: Use fundBatchWallet() instead of direct transfer"); require(to != airdropWallet, "RwaToken: Use fundAirdropWallet() instead of direct transfer"); super._update(from, to, value); } function decimals() public pure override returns (uint8) { return 3; } function tokenImageUrl() public view returns (string memory) { return _tokenImageUrl; } function websiteUrl() public view returns (string memory) { return _websiteUrl; } function burn(uint256 amount) public whenNotFrozen(_msgSender()) { require(amount > 0, "RwaToken: Burn amount must be > 0"); _burn(_msgSender(), amount); } function pause() public onlyRole(PAUSER_ROLE) { _pause(); } function unpause() public onlyRole(PAUSER_ROLE) { _unpause(); } function setTokenImageUrl(string memory newUrl) public onlyRole(CONFIGURATOR_ROLE) { _tokenImageUrl = newUrl; emit TokenImageUrlUpdated(newUrl); } function setWebsiteUrl(string memory newUrl) public onlyRole(CONFIGURATOR_ROLE) { _websiteUrl = newUrl; emit WebsiteUrlUpdated(newUrl); } function setSupportWallet(address newWallet) public onlyRole(DEFAULT_ADMIN_ROLE) { require(newWallet != address(0), "RwaToken: Invalid address"); supportWallet = newWallet; emit SupportWalletUpdated(newWallet); } function freezeWallet(address account, bool freeze) public onlyRole(FREEZER_ROLE) { require(account != address(0), "RwaToken: Cannot freeze zero address"); frozenWallets[account] = freeze; if (freeze) emit WalletFrozen(account); else emit WalletUnfrozen(account); } function fundBatchWallet(uint256 amount) public onlyRole(DISTRIBUTOR_ROLE) nonReentrant { require(amount > 0, "RwaToken: Amount must be > 0"); uint256 currentBalance = balanceOf(batchWallet); if (currentBalance > 0) _burn(batchWallet, currentBalance); _transfer(_msgSender(), batchWallet, amount); emit BatchWalletFunded(amount); } function fundAirdropWallet(uint256 amount) public onlyRole(DISTRIBUTOR_ROLE) nonReentrant { require(amount > 0, "RwaToken: Amount must be > 0"); uint256 currentBalance = balanceOf(airdropWallet); if (currentBalance > 0) _burn(airdropWallet, currentBalance); _transfer(_msgSender(), airdropWallet, amount); emit AirdropWalletFunded(amount); } function distributeFromBatch(address[] calldata users, uint256[] calldata amounts) public onlyRole(DISTRIBUTOR_ROLE) nonReentrant { distributeFrom(batchWallet, users, amounts); } function distributeFromAirdrop(address[] calldata users, uint256[] calldata amounts) public onlyRole(DISTRIBUTOR_ROLE) nonReentrant { distributeFrom(airdropWallet, users, amounts); } function distributeFrom(address fromWallet, address[] calldata users, uint256[] calldata amounts) internal { uint256 usersLength = users.length; require(usersLength > 0 && usersLength <= DISTRIBUTION_LIMIT && usersLength == amounts.length, "RwaToken: Invalid distribution arrays"); uint256 totalAmount = 0; for (uint256 i = 0; i < usersLength; ) { totalAmount += amounts[i]; unchecked { ++i; } } require(balanceOf(fromWallet) >= totalAmount, "RwaToken: Insufficient funds in source wallet"); for (uint256 i = 0; i < usersLength; ) { require(users[i] != address(0), "RwaToken: Invalid recipient"); _transfer(fromWallet, users[i], amounts[i]); unchecked { ++i; } } emit TokensDistributed(fromWallet, totalAmount, usersLength); } function rescueErc20(address tokenAddress, address to, uint256 amount) public onlyRole(RESCUER_ROLE) { require(amount > 0, "RwaToken: Amount must be > 0"); require(tokenAddress != address(this), "RwaToken: Cannot rescue self"); IERC20(tokenAddress).safeTransfer(to, amount); emit TokensRescued(tokenAddress, to, amount); } }