S

Solidarity Basket

Semua artikel

Kontrak Pintar / Bahasa Indonesia

Bahasa Indonesia

Kode Sumber

Kode sumber Solidity lengkap SolidarityBasket. Alamat: 0x555400DE5f287bB7E2a7F1C1E26fEa9cDCdB2877 · MIT · ^0.8.20

Salin seluruh file ke SolidarityBasket.sol.

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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title SolidarityBasket
 * @notice "الخزنة الآمنة" - العقد الذكي الوحيد المسؤول عن استقبال وسحب الأموال.
 *
 * مبدأ التصميم المتعمّد:
 * - هذا العقد لا يعرف شيئاً عن "الدولار". كل تحويل (USD -> BNB أو USD -> USDT)
 *   يحدث في الواجهة الأمامية (Off-chain) قبل إرسال المعاملة.
 * - العقد يستقبل فقط الرقم النهائي الجاهز (Wei أو أصغر وحدة USDT) ويسجّله.
 * - لا يوجد Oracle داخل العقد لتقليل التعقيد وتقليل مساحة الهجوم (attack surface).
 */
contract SolidarityBasket is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    // عنوان عقد USDT على BSC (BEP-20) - يُمرَّر عند النشر، وليس مُثبَّتاً بالكود
    // حتى نقدر نستخدم نفس العقد على Testnet بعنوان USDT تجريبي مختلف.
    IERC20 public immutable usdtToken;

    // رصيد كل مستفيد (صاحب صفحة) منفصل داخل نفس الخزنة
    // هذا يمهّد لمرحلة "تعدد الصفحات" المستقبلية دون تعديل العقد لاحقاً
    mapping(address => uint256) public bnbBalanceOf;
    mapping(address => uint256) public usdtBalanceOf;

    event BNBDeposited(address indexed donor, address indexed beneficiary, uint256 amountWei, uint256 timestamp);
    event USDTDeposited(address indexed donor, address indexed beneficiary, uint256 amountRaw, uint256 timestamp);
    event BNBWithdrawn(address indexed beneficiary, address indexed to, uint256 amountWei);
    event USDTWithdrawn(address indexed beneficiary, address indexed to, uint256 amountRaw);

    error ZeroAmount();
    error InsufficientBalance();
    error ZeroAddress();

    constructor(address _usdtToken, address _initialOwner) Ownable(_initialOwner) {
        if (_usdtToken == address(0)) revert ZeroAddress();
        usdtToken = IERC20(_usdtToken);
    }

    /**
     * @notice إيداع BNB لصالح مستفيد معيّن (صاحب صفحة).
     * @param beneficiary عنوان محفظة المستفيد الذي سيُحسب له الرصيد.
     * @dev المبلغ يُحدَّد بـ msg.value (Wei) الذي حسبته الواجهة الأمامية مسبقاً
     *      من السعر اللحظي BNB/USD.
     */
    function depositBNB(address beneficiary) external payable nonReentrant {
        if (beneficiary == address(0)) revert ZeroAddress();
        if (msg.value == 0) revert ZeroAmount();

        bnbBalanceOf[beneficiary] += msg.value;
        emit BNBDeposited(msg.sender, beneficiary, msg.value, block.timestamp);
    }

    /**
     * @notice إيداع USDT لصالح مستفيد معيّن.
     * @param beneficiary عنوان محفظة المستفيد.
     * @param amount الكمية بأصغر وحدة (raw units) - تنبيه: USDT على BSC = 18 decimals.
     * @dev يجب أن يكون المتبرع قد نفّذ usdtToken.approve(address(this), amount) مسبقاً.
     */
    function depositUSDT(address beneficiary, uint256 amount) external nonReentrant {
        if (beneficiary == address(0)) revert ZeroAddress();
        if (amount == 0) revert ZeroAmount();

        // نمط Checks-Effects-Interactions: نحدّث الحالة قبل نقل التوكن
        usdtBalanceOf[beneficiary] += amount;

        usdtToken.safeTransferFrom(msg.sender, address(this), amount);
        emit USDTDeposited(msg.sender, beneficiary, amount, block.timestamp);
    }

    /**
     * @notice سحب BNB - المستفيد يسحب رصيده الخاص فقط.
     * @dev لاحظ: هذه ليست onlyOwner، بل كل مستفيد يسحب رصيده هو فقط.
     *      هذا أكثر أماناً من جعل "مدير واحد" يسحب أموال الجميع.
     */
    function withdrawBNB(address payable to, uint256 amount) external nonReentrant {
        if (to == address(0)) revert ZeroAddress();
        if (amount == 0) revert ZeroAmount();
        if (bnbBalanceOf[msg.sender] < amount) revert InsufficientBalance();

        bnbBalanceOf[msg.sender] -= amount;

        (bool success, ) = to.call{value: amount}("");
        require(success, "BNB transfer failed");

        emit BNBWithdrawn(msg.sender, to, amount);
    }

    /**
     * @notice سحب USDT - المستفيد يسحب رصيده الخاص فقط.
     */
    function withdrawUSDT(address to, uint256 amount) external nonReentrant {
        if (to == address(0)) revert ZeroAddress();
        if (amount == 0) revert ZeroAmount();
        if (usdtBalanceOf[msg.sender] < amount) revert InsufficientBalance();

        usdtBalanceOf[msg.sender] -= amount;
        usdtToken.safeTransfer(to, amount);

        emit USDTWithdrawn(msg.sender, to, amount);
    }

    /**
     * @notice دالة طوارئ فقط لمالك العقد (لحالات نادرة: خطأ في عنوان مستفيد، إلخ)
     *      لا تُستخدم للسحب الروتيني - السحب الروتيني يكون عبر withdrawBNB/withdrawUSDT
     *      من قبل المستفيد نفسه فقط.
     */
    function emergencyWithdrawStuckBNB(address payable to, uint256 amount) external onlyOwner nonReentrant {
        if (to == address(0)) revert ZeroAddress();
        require(amount <= address(this).balance, "Exceeds contract balance");
        (bool success, ) = to.call{value: amount}("");
        require(success, "Transfer failed");
    }
}