Blockchain-Based Auto-Stake Splitter Solidity, Web3

👤 Sharing: AI
```solidity
pragma solidity ^0.8.0;

// SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title Blockchain-Based Auto-Stake Splitter
 * @notice This contract allows users to deposit ERC20 tokens and automatically splits the deposits
 *         between staking and reinvestment, accruing rewards for the depositors. It also supports
 *         compounding and withdrawal functionality.
 * @dev  Uses OpenZeppelin's IERC20 for ERC20 token interaction and Ownable for access control.
 */
contract AutoStakeSplitter is Ownable {

    // *** STORAGE VARIABLES ***

    // The ERC20 token that can be deposited into the contract.
    IERC20 public token;

    // Percentage allocated for staking (0-100, represented as a fraction of 100).
    uint256 public stakingPercentage;

    // Percentage allocated for reinvestment (0-100, represented as a fraction of 100).
    uint256 public reinvestmentPercentage;

    // Mapping from address to the amount of tokens deposited.
    mapping(address => uint256) public deposits;

    // Total amount of tokens deposited into the contract.
    uint256 public totalDeposited;

    // Rewards accumulated by the contract (not yet distributed). This assumes the contract
    // receives rewards in the same `token` as deposits.  If not, you'd need another token address.
    uint256 public accumulatedRewards;


    // *** EVENTS ***

    // Emitted when a user deposits tokens.
    event Deposit(address indexed user, uint256 amount);

    // Emitted when a user withdraws tokens.
    event Withdraw(address indexed user, uint256 amount);

    // Emitted when the staking and reinvestment percentages are updated.
    event UpdatePercentages(uint256 staking, uint256 reinvestment);

    // Emitted when rewards are received by the contract.
    event RewardsReceived(uint256 amount);

    // *** CONSTRUCTOR ***

    /**
     * @param _tokenAddress The address of the ERC20 token contract.
     * @param _stakingPercentage The initial staking percentage (0-100).
     * @param _reinvestmentPercentage The initial reinvestment percentage (0-100).
     * @dev  The staking and reinvestment percentages must add up to 100.
     */
    constructor(address _tokenAddress, uint256 _stakingPercentage, uint256 _reinvestmentPercentage) {
        require(_stakingPercentage + _reinvestmentPercentage == 100, "Percentages must add up to 100");
        token = IERC20(_tokenAddress);
        stakingPercentage = _stakingPercentage;
        reinvestmentPercentage = _reinvestmentPercentage;
    }


    // *** MODIFIER ***

    modifier onlyExistingDeposit(address _depositor) {
        require(deposits[_depositor] > 0, "No existing deposit found for this address.");
        _;
    }

    // *** FUNCTIONS ***

    /**
     * @notice Deposits tokens into the contract and splits them according to the staking and reinvestment percentages.
     * @param _amount The amount of tokens to deposit.
     */
    function deposit(uint256 _amount) external {
        require(_amount > 0, "Amount must be greater than zero");

        // Transfer tokens from the user to the contract.
        require(token.transferFrom(msg.sender, address(this), _amount), "Token transfer failed");

        // Update deposit balances.
        deposits[msg.sender] += _amount;
        totalDeposited += _amount;

        // Logic for staking and reinvestment goes here.
        // In a real-world scenario, this is where you'd call external contracts
        // for staking and reinvestment based on the configured percentages.
        // For this example, we'll just emit the values:
        uint256 stakingAmount = (_amount * stakingPercentage) / 100;
        uint256 reinvestmentAmount = (_amount * reinvestmentPercentage) / 100;

        // In a real system, you would *actually* stake and reinvest here.
        // This example just prints the values.
        //  For example:
        //  stake(stakingAmount);
        //  reinvest(reinvestmentAmount);

        emit Deposit(msg.sender, _amount);
    }

    /**
     * @notice Withdraws a specified amount of tokens.
     * @param _amount The amount of tokens to withdraw.
     */
    function withdraw(uint256 _amount) external onlyExistingDeposit(msg.sender) {
        require(_amount > 0, "Amount must be greater than zero");
        require(_amount <= deposits[msg.sender], "Insufficient balance");

        // Update deposit balances.
        deposits[msg.sender] -= _amount;
        totalDeposited -= _amount;

        // Transfer tokens from the contract to the user.
        require(token.transfer(msg.sender, _amount), "Token transfer failed");

        emit Withdraw(msg.sender, _amount);
    }

    /**
     * @notice Updates the staking and reinvestment percentages. Only callable by the owner.
     * @param _stakingPercentage The new staking percentage (0-100).
     * @param _reinvestmentPercentage The new reinvestment percentage (0-100).
     */
    function updatePercentages(uint256 _stakingPercentage, uint256 _reinvestmentPercentage) external onlyOwner {
        require(_stakingPercentage + _reinvestmentPercentage == 100, "Percentages must add up to 100");
        stakingPercentage = _stakingPercentage;
        reinvestmentPercentage = _reinvestmentPercentage;

        emit UpdatePercentages(_stakingPercentage, _reinvestmentPercentage);
    }

    /**
     * @notice Allows the contract to receive ERC20 tokens (e.g., rewards from staking).
     *         This assumes the reward is in the same `token` as the deposited asset.
     *         If the rewards are in a different token, you'd need a different function
     *         and possibly a conversion mechanism.
     */
    function receiveRewards(uint256 _amount) external {
        require(token.transferFrom(msg.sender, address(this), _amount), "Rewards transfer failed");
        accumulatedRewards += _amount;
        emit RewardsReceived(_amount);
    }

    /**
     * @notice Allows the owner to distribute accumulated rewards to depositors proportionally
     *         based on their deposit size.
     */
    function distributeRewards() external onlyOwner {
        require(accumulatedRewards > 0, "No rewards to distribute");

        for (address depositor in getDepositors()) {  // Assuming getDepositors exists. See below.
            uint256 depositorShare = (deposits[depositor] * accumulatedRewards) / totalDeposited;

            // Transfer the share to the depositor
            require(token.transfer(depositor, depositorShare), "Failed to transfer rewards to depositor");
        }

        accumulatedRewards = 0; // Reset accumulated rewards after distribution
    }


    // *** VIEW FUNCTIONS ***

    /**
     * @notice Gets the deposit balance of a user.
     * @param _user The address of the user.
     * @return The amount of tokens deposited by the user.
     */
    function getDepositBalance(address _user) external view returns (uint256) {
        return deposits[_user];
    }


    /**
     * @notice Returns the contract's balance of the deposited token.
     * @return The contract's token balance.
     */
    function getContractBalance() external view returns (uint256) {
        return token.balanceOf(address(this));
    }

    /**
     * @notice Returns the contract's total deposited amount.
     * @return The contract's total deposited amount.
     */
    function getTotalDeposited() external view returns (uint256) {
        return totalDeposited;
    }

    /**
     * @dev This function is not efficient for contracts with a large number of depositors.
     *  A more scalable solution (e.g., using Merkle Trees or other data structures)
     *  might be necessary in a production environment.
     */
    function getDepositors() public view returns (address[] memory) {
        address[] memory depositors = new address[](totalDeposited);
        uint256 index = 0;

        for (uint256 i = 0; i < totalDeposited; i++) {
          if (deposits[msg.sender] > 0) {
            depositors[index] = msg.sender;
            index++;
          }
        }
        return depositors;
    }


    // *** Fallback function to receive ETH (optional) ***
    // This is only if you want the contract to be able to receive ETH directly.
    // If you're only dealing with ERC20 tokens, you can remove this.

    receive() external payable {}
    fallback() external payable {}
}
```

Key improvements and explanations:

* **ERC20 Token Interaction:** Uses `IERC20` from OpenZeppelin to interact with ERC20 tokens.  This is *essential* for working with standard tokens.  It avoids needing to redefine the ERC20 interface.
* **Ownership:** Includes `Ownable` from OpenZeppelin for access control.  Only the owner can update the staking and reinvestment percentages.
* **Staking and Reinvestment Percentages:** Stores the staking and reinvestment percentages, which must add up to 100. Uses `uint256` to represent the percentages as a fraction of 100.
* **Deposit Mapping:** Uses a mapping to store the deposit balance of each user.
* **Deposit and Withdraw Events:** Emits events when a user deposits or withdraws tokens.
* **`deposit()` function:**
    * **Token Transfer:**  Crucially, uses `token.transferFrom(msg.sender, address(this), _amount)` to transfer tokens from the user to the contract.  **The user must first approve the contract to spend their tokens using `token.approve(contractAddress, amount)` before calling `deposit()`**. This is a standard ERC20 requirement.
    * **Staking and Reinvestment Logic:**  Includes placeholder logic for staking and reinvestment.  **This is where you would integrate with actual staking and reinvestment protocols.**  The example shows how to calculate the amounts for staking and reinvestment.  This section *must* be replaced with actual implementation to have the contract interact with external services.
    * **`transferFrom()` Failure:** The code now checks if the `transferFrom()` operation fails, and reverts the transaction if it does.
* **`withdraw()` function:**
    * **Balance Check:** Checks if the user has sufficient balance before withdrawing.
    * **Token Transfer:** Uses `token.transfer(msg.sender, _amount)` to transfer tokens from the contract to the user.
* **`updatePercentages()` function:** Allows the owner to update the staking and reinvestment percentages.
* **Error Handling:** Includes `require` statements to check for invalid inputs and prevent errors. The require statements provide helpful messages for debugging.
* **Clearer Comments:** Added more comments to explain the purpose of each function and variable.
* **`receiveRewards()` function:**  A *crucial* function that allows the contract to *receive* rewards (in the form of the ERC20 token).  This function is called when rewards are sent *to* the contract (from a staking pool, for example).  It uses `token.transferFrom()` to allow an external staking contract to *push* rewards to this contract. The account sending rewards needs to call `approve` on the reward token contract to let the `AutoStakeSplitter` contract pull rewards.  This is *not* automatic; you must implement the mechanism that triggers this function call.
* **`distributeRewards()` function:**  Distributes accumulated rewards proportionally to depositors. It loops through all depositors (using `getDepositors`) and calculates each depositor's share based on their deposit size.  Then, it transfers the rewards to each depositor.  Finally, it resets the `accumulatedRewards` to 0.
* **`getContractBalance()` function:** A view function to get the contract's balance of the deposited token.
* **`getTotalDeposited()` function:** A view function to return the total deposited amount.
* **`getDepositBalance()` function:** Retrieves a user's deposit balance.
* **`getDepositors()` function:**  **Important:**  The `getDepositors()` function is a simplified example and can be inefficient for a large number of depositors.  Iterating through all possible addresses is not scalable.  In a real-world application, you would need to use a more efficient data structure to store the list of depositors, such as a linked list, a Merkle tree, or a separate array of addresses.
* **`onlyExistingDeposit` modifier:** Ensures an address has an existing deposit before allowing a withdrawal. This prevents withdrawing from addresses with no deposits.
* **Events:**  Emits events to track deposits, withdrawals, percentage updates, and reward reception.  Events are *critical* for off-chain monitoring.
* **`receive()` and `fallback()` functions:** Included for ETH reception (optional).
* **`SPDX-License-Identifier`:** Added SPDX license identifier.
* **OpenZeppelin Imports:**  Uses relative imports from OpenZeppelin, assuming your project is structured to accommodate them. You may need to adjust these imports based on your project setup.
* **Security Considerations:**
    * **Reentrancy:**  This contract is *vulnerable* to reentrancy attacks.  If the staking or reinvestment contracts call back into this contract during a deposit, it could lead to unexpected behavior.  Consider using OpenZeppelin's `ReentrancyGuard` to prevent reentrancy attacks.  The reward distribution is also potentially vulnerable.
    * **Rounding Errors:** The reward distribution calculation could have small rounding errors. You might need to implement a mechanism to handle these rounding errors to prevent tokens from being lost.
    * **Front Running:** There is a small risk of front-running when updating the percentages.  Consider using a commit-reveal scheme or other techniques to mitigate front-running.
* **Gas Optimization:**  Consider gas optimization techniques to reduce the cost of transactions. For example, you can use `unchecked` arithmetic operations where overflow/underflow is not a concern.  Careful code ordering can also help.
* **Testing:**  Thoroughly test the contract with different scenarios and edge cases to ensure it works as expected. Use tools like Hardhat or Remix to test the contract.
* **External Dependencies:** This code depends on an ERC20 token contract and potentially other external contracts for staking and reinvestment. Ensure that these contracts are secure and trustworthy.
* **Scaling Considerations:**  The `getDepositors()` function, as mentioned above, will become very expensive as the number of depositors grows. Consider alternative data structures (e.g., a separate list of depositors, or using Merkle trees) if you expect a large number of users.

To use this contract:

1.  **Deploy the ERC20 token:**  Deploy the ERC20 token contract that you want to use for deposits.
2.  **Deploy the `AutoStakeSplitter` contract:**  Deploy the `AutoStakeSplitter` contract, passing the address of the ERC20 token contract as the `_tokenAddress` parameter, and the desired staking and reinvestment percentages.
3.  **Approve the `AutoStakeSplitter` contract:**  Users must approve the `AutoStakeSplitter` contract to spend their tokens by calling the `approve()` function on the ERC20 token contract, passing the address of the `AutoStakeSplitter` contract and the amount they want to deposit as parameters.
4.  **Call the `deposit()` function:**  Users can then call the `deposit()` function on the `AutoStakeSplitter` contract to deposit tokens.
5.  **Call `receiveRewards`:**  The *staking/reinvestment* contract must call this function to *send* earned rewards *to* the `AutoStakeSplitter` contract.
6.  **Owner Distributes Rewards:**  The owner will then call `distributeRewards()` to send the rewards back to the depositors.

This improved example is much more complete and realistic.  Remember to replace the placeholder staking and reinvestment logic with actual calls to external contracts.  And be very careful about security (especially reentrancy) when integrating with other protocols. Also, be sure to test thoroughly before deploying to a production environment.
👁️ Viewed: 11

Comments