EURTreasuryEthereum

Description:

ERC20 token contract. Standard implementation for fungible tokens on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

/**
 *Submitted for verification at Etherscan.io on 2025-10-10
*/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/*
 * EUR Treasury (Ethereum)
 * - Compatible con The Digital Euro (EUR) en la red Ethereum
 * - Dirección del token EUR: 0xA3Ec40cC9736ad0064630BfD60f2876D7762E3B2
 */

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address a) external view returns (uint256);
    function allowance(address o, address s) external view returns (uint256);
    function approve(address s, uint256 v) external returns (bool);
    function transfer(address to, uint256 v) external returns (bool);
    function transferFrom(address f, address t, uint256 v) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

library Address {
    function isContract(address a) internal view returns (bool) {
        return a.code.length > 0;
    }
}

library SafeERC20 {
    using Address for address;

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _call(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _call(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        _call(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function _call(IERC20 token, bytes memory data) private {
        (bool ok, bytes memory ret) = address(token).call(data);
        require(ok, "SafeERC20: call failed");
        if (ret.length > 0) require(abi.decode(ret, (bool)), "SafeERC20: op failed");
    }
}

interface ITreasuryReceiver {
    function onTreasuryReceived(address from, address token, uint256 amount, bytes calldata data) external returns (bytes4);
}

abstract contract NonReentrant {
    uint256 private _entered;
    modifier nonReentrant() {
        require(_entered == 0, "REENTRANCY");
        _entered = 1;
        _;
        _entered = 0;
    }
}

contract EURTreasuryEthereum is NonReentrant {
    using SafeERC20 for IERC20;

    uint256 private constant BPS_DENOM = 10_000;
    bytes4  private constant MAGIC_ACK = 0x150b7a02;

    IERC20 public immutable eur; // The Digital Euro (EUR)
    address public admin;
    bool public treasuryActive;

    uint16 public goldBps = 5000;
    uint16 public opsBps = 1000;
    uint16 public rewardsBps = 4000;

    uint256 public accountedBalance;
    uint256 public goldReserve;
    uint256 public opsReserve;
    uint256 public rewardsReserve;

    address public feeCollector;
    mapping(address => address) public referrerOf;
    mapping(address => bool) public referralLocked;
    mapping(address => bool) public excludedFromRewards;

    address public pendingMigration;
    uint256 public migrationETA;
    uint256 public migrationDelay = 72 hours;

    mapping(address => bool) public rescueWhitelist;
    mapping(address => uint256) public rescueCapPerToken;
    mapping(address => uint256) public rescuedSoFar;

    event AdminChanged(address indexed oldAdmin, address indexed newAdmin);
    event TreasuryActivated(address indexed by, uint256 at);
    event TreasuryDeactivated(address indexed by, uint256 at);
    event SplitsChanged(uint16 goldBps, uint16 opsBps, uint16 rewardsBps);
    event Deposited(address indexed from, uint256 amount, bool asFees);
    event BucketsSettled(uint256 delta, uint256 addGold, uint256 addOps, uint256 addRewards);
    event RewardsSent(address indexed to, uint256 amount);
    event OpsSent(address indexed to, uint256 amount);
    event GoldCommitted(address indexed to, uint256 amount, bytes32 refId);
    event ExcludedSet(address indexed account, bool excluded);
    event FeeCollectorSet(address indexed collector);
    event ReferralRegistered(address indexed user, address indexed referrer);
    event MigrationQueued(address indexed newTreasury, uint256 eta);
    event MigrationCancelled();
    event MigrationExecuted(address indexed newTreasury, uint256 amount, bool acked);
    event RescueWhitelistSet(address indexed token, bool allowed, uint256 cap);
    event TokenRescued(address indexed token, address indexed to, uint256 amount);

    error NotAdmin();
    error NotActive();
    error ZeroAddress();
    error BadSplits();
    error NotAllowed();
    error TooSoon();
    error CapExceeded();
    error EURNotRescuable();

    modifier onlyAdmin() {
        if (msg.sender != admin) revert NotAdmin();
        _;
    }

    modifier whenActive() {
        if (!treasuryActive) revert NotActive();
        _;
    }

    constructor(address eurToken, address initialAdmin) {
        if (eurToken == address(0) || initialAdmin == address(0)) revert ZeroAddress();
        eur = IERC20(eurToken); // Token EUR en Ethereum
        admin = initialAdmin;
        excludedFromRewards[address(0)] = true;
        excludedFromRewards[address(this)] = true;
    }

    function setAdmin(address newAdmin) external onlyAdmin {
        if (newAdmin == address(0)) revert ZeroAddress();
        emit AdminChanged(admin, newAdmin);
        admin = newAdmin;
    }

    function activateTreasury() external onlyAdmin {
        treasuryActive = true;
        emit TreasuryActivated(msg.sender, block.timestamp);
    }

    function deactivateTreasury() external onlyAdmin {
        treasuryActive = false;
        emit TreasuryDeactivated(msg.sender, block.timestamp);
    }

    function setSplits(uint16 _gold, uint16 _ops, uint16 _rewards) external onlyAdmin {
        if (_gold + _ops + _rewards != BPS_DENOM) revert BadSplits();
        goldBps = _gold; opsBps = _ops; rewardsBps = _rewards;
        emit SplitsChanged(_gold, _ops, _rewards);
    }

    function setFeeCollector(address collector) external onlyAdmin {
        if (collector == address(0)) revert ZeroAddress();
        feeCollector = collector;
        emit FeeCollectorSet(collector);
    }

    function setExcludedFromRewards(address account, bool excluded) external onlyAdmin {
        excludedFromRewards[account] = excluded;
        emit ExcludedSet(account, excluded);
    }

    function deposit(address from, uint256 amount, bool asFees) external nonReentrant {
        if (!treasuryActive) revert NotActive();
        if (msg.sender != admin && msg.sender != feeCollector) revert NotAllowed();
        require(from != address(0) && amount > 0, "bad params");

        eur.safeTransferFrom(from, address(this), amount);
        emit Deposited(from, amount, asFees);
        _settleBuckets();
    }

    function settleBuckets() external whenActive nonReentrant {
        _settleBuckets();
    }

    function _settleBuckets() internal {
        uint256 bal = eur.balanceOf(address(this));
        if (bal <= accountedBalance) return;
        uint256 delta = bal - accountedBalance;

        uint256 addGold = (delta * goldBps) / BPS_DENOM;
        uint256 addOps = (delta * opsBps) / BPS_DENOM;
        uint256 addRewards = delta - addGold - addOps;

        goldReserve += addGold;
        opsReserve += addOps;
        rewardsReserve += addRewards;
        accountedBalance = bal;

        emit BucketsSettled(delta, addGold, addOps, addRewards);
    }

    function sendRewards(address to, uint256 amount) external onlyAdmin whenActive nonReentrant {
        require(to != address(0) && !excludedFromRewards[to], "invalid to");
        require(amount > 0 && amount <= rewardsReserve, "amount");
        rewardsReserve -= amount;
        accountedBalance -= amount;
        eur.safeTransfer(to, amount);
        emit RewardsSent(to, amount);
    }

    function sendOps(address to, uint256 amount) external onlyAdmin whenActive nonReentrant {
        require(to != address(0), "invalid to");
        require(amount > 0 && amount <= opsReserve, "amount");
        opsReserve -= amount;
        accountedBalance -= amount;
        eur.safeTransfer(to, amount);
        emit OpsSent(to, amount);
    }

    function commitGold(address toVault, uint256 amount, bytes32 refId)
        external
        onlyAdmin
        whenActive
        nonReentrant
    {
        require(toVault != address(0), "vault=0");
        require(amount > 0 && amount <= goldReserve, "amount");
        goldReserve -= amount;
        accountedBalance -= amount;
        eur.safeTransfer(toVault, amount);
        emit GoldCommitted(toVault, amount, refId);
    }

    function registerReferral(address user, address referrer) external onlyAdmin {
        require(user != address(0) && referrer != address(0), "0 addr");
        require(!referralLocked[user], "locked");
        referrerOf[user] = referrer;
        referralLocked[user] = true;
        emit ReferralRegistered(user, referrer);
    }

    function setMigrationDelay(uint256 newDelay) external onlyAdmin {
        require(newDelay >= 24 hours && newDelay <= 14 days, "delay bounds");
        migrationDelay = newDelay;
    }

    function queueMigration(address newTreasury) external onlyAdmin {
        require(newTreasury != address(0) && Address.isContract(newTreasury), "bad target");
        pendingMigration = newTreasury;
        migrationETA = block.timestamp + migrationDelay;
        emit MigrationQueued(newTreasury, migrationETA);
    }

    function cancelMigration() external onlyAdmin {
        pendingMigration = address(0);
        migrationETA = 0;
        emit MigrationCancelled();
    }

    function executeMigration(bytes calldata data) external onlyAdmin nonReentrant {
        require(pendingMigration != address(0) && block.timestamp >= migrationETA, "not ready");
        _settleBuckets();

        uint256 amt = eur.balanceOf(address(this));
        accountedBalance = 0;
        goldReserve = 0;
        opsReserve = 0;
        rewardsReserve = 0;

        address target = pendingMigration;
        pendingMigration = address(0);
        migrationETA = 0;

        eur.safeTransfer(target, amt);

        bool ack = false;
        if (Address.isContract(target)) {
            try ITreasuryReceiver(target).onTreasuryReceived(address(this), address(eur), amt, data)
                returns (bytes4 r) {
                if (r == MAGIC_ACK) ack = true;
            } catch { ack = false; }
        }
        emit MigrationExecuted(target, amt, ack);
    }

    function setRescueRule(address token, bool allowed, uint256 cap) external onlyAdmin {
        if (token == address(eur)) revert EURNotRescuable();
        rescueWhitelist[token] = allowed;
        rescueCapPerToken[token] = cap;
        emit RescueWhitelistSet(token, allowed, cap);
    }

    function rescueERC20(address token, address to, uint256 amount) external onlyAdmin nonReentrant {
        if (token == address(eur)) revert EURNotRescuable();
        require(rescueWhitelist[token], "not whitelisted");
        require(to != address(0) && amount > 0, "bad params");

        uint256 cap = rescueCapPerToken[token];
        if (cap > 0) {
            uint256 newTotal = rescuedSoFar[token] + amount;
            if (newTotal > cap) revert CapExceeded();
            rescuedSoFar[token] = newTotal;
        }

        IERC20(token).safeTransfer(to, amount);
        emit TokenRescued(token, to, amount);
    }

    function eurBalance() external view returns (uint256) {
        return eur.balanceOf(address(this));
    }

    function unaccountedEUR() external view returns (uint256) {
        uint256 bal = eur.balanceOf(address(this));
        return (bal > accountedBalance) ? bal - accountedBalance : 0;
    }
}

Tags:
ERC20, Token|addr:0xdb3ba2bf003f12452d03b7fed9a798fc782d06c5|verified:true|block:23546648|tx:0x77b6afa398538793e09db3b5720b54a37e7faa88dbd6175c6513a9b5f114d8bc|first_check:1760094687

Submitted on: 2025-10-10 13:11:28

Comments

Log in to comment.

No comments yet.