Farming

TokenFarm is designed to implement a smart contract-based farming distribution incentive system. Its primary functions include user staking and unstaking of Yield Tokens, and accessing system rewards.

Contract Functions:

  • Users can stake Yield Token (YT, ERC20 Token) acquired by providing liquidity to the fund pool to participate in mining and receive rewards.

  • Users can deposit multiple mining orders, with each staking order calculating earnings separately. Different terms will have different earnings multipliers.

  • After each day, earnings continue to accrue for each order until the staked assets are withdrawn voluntarily.

  • Earnings are divided into two parts: the first part comes from block rewards, and the second part comes from earnings generated by the Position Token business.

Main Variables:

  • TOKEN: Address of the token used for earnings distribution.

  • lpToken: Contract address of the staked asset.

  • tokenPerBlock: Number of tokens produced per block on average.

  • proTotal: Total number of product orders.

  • startBlock: Block at which mining begins.

  • lastRewardBlock: Last updated block for rewards.

  • accTokenPerShare: Accumulated token rewards per share.

  • accDividendPerShare: Accumulated dividend rewards per share.

  • totalPower: Total computing power.

  • totalDividend: Accumulated dividend funds.

Structures:

  • Product: Defines the product cycle duration and multiplier.

  • Order: Defines the ID, participation amount, computing power, and order time.

  • UserInfo: Defines the user's computing power, staked amount, earned rewards, pending rewards, reward debts, and other information.

Events:

  • Deposit: Triggered when a user stakes their assets.

  • Withdraw: Triggered when a user withdraws their earnings.

Functions:

  • initialize(): Initialize the basic parameters of the contract.

  • pendingToken(): Check the pending rewards waiting to be claimed by the user.

  • updatePool(): Update the total hash power and the accumulated rewards per share.

  • deposit(): Allow users to deposit staked tokens for mining participation.

  • withdraw(): Allow users to withdraw their earnings.

  • harvestOrderPrincipal(): Allow users to withdraw their principal amount.

  • getAllProducts(): Retrieve information about all products within the system.

  • argInfo(): Retrieve aggregated information such as a user's staked assets, authorized limits, pending rewards, etc.

  • getUserOrders(): Retrieve a user's current and historical orders.

  • intoFee(): Increase Farming2 dividend rewards for the "market contract" address.

  • getToken(): Return the address of the reward token.

  • getLpToken(): Return the address of the LP token used for mining.

  • getUserInfo(): Return detailed information about a specified user.

  • getMultiplier(): Calculate the reward multiplier based on the block range.

Security and Upgradability:

  • Use the SafeMath library to prevent integer overflow.

  • Use the SafeERC20 library to safely handle ERC20 tokens.

Contract behaviors

Hashrate

Hashrate is a critical concept that determines a user's share in mining activities and the amount of rewards they qualify to receive. Hashrate can be understood as a weighted representation of the assets a user stakes, influencing the distribution ratio of rewards. In the contract logic, each order receives a separate hashrate value based on the periodic hashrate multiplier. The sum of hashrates from all orders represents the user's effective hashrate.

Concept of Hashrate:

  • Hashrate represents the contribution of a user to a mining pool.

  • The more assets a user stakes, the higher their hashrate, which corresponds to a larger share of rewards they can receive.

  • Hashrate is also associated with specific multipliers, meaning different products and deposit options offer varying hashrate multipliers.

  • Hashrate decreases only when a user releases their staked orders.

The role of hashrate in business logic:

  • Reward Calculation: Hashrate is used to calculate the amount of rewards a user should receive. The contract allocates rewards based on the proportion of a user's hashrate to the total hashrate.

  • Updating Total Hashrate: When users deposit or withdraw assets, the contract updates the total hashrate (totalPower), which is the sum of all users' hashrates.

  • Reward Debt Management: Each user's UserInfo structure includes a rewardDebt field, which tracks the rewards accumulated by the user since the last update. Hashrate plays a crucial role in calculating this rewardDebt for the user.

Here's the translation for the concepts you provided:

  • Order Hashrate: In the Order structure, the power field represents the hashrate of each order.

  • User Hashrate: In the UserInfo structure, the power field represents the current total hashrate of the user.

  • Reward Debt Update: In the deposit and _withdraw functions, the user's rewardDebt is updated based on their hashrate and the accumulated rewards per share (accTokenPerShare).

  • Reward Distribution: In the pendingToken function, the user's pending rewards are calculated based on their hashrate and the accumulated rewards per share.

  • Total Hashrate Maintenance: In the deposit and harvestOrderPrincipal functions, the total hashrate (totalPower) increases or decreases accordingly as users deposit or withdraw assets.

User Deposit Process

When a user initiates a deposit operation in the smart contract, the following sequence of events occurs:

  1. Calling the deposit function: The user begins the deposit process by calling the deposit function and providing the product ID (_pid) and the amount (_amount) they wish to stake.

  2. Updating pool rewards: The contract first calls the updatePool function to ensure all reward variables are up to date, which affects the calculation of user rewards.

  3. Checking user's existing balance: If the user had a previous balance, the contract calculates and transfers their pending rewards to their earnings balance.

  4. Transferring user's staked tokens: The user's YT tokens are transferred from their account to the contract's address. This is achieved by calling IERC20(lpToken).safeTransferFrom.

  5. Creating a new order: A new order is created for the user, including the product ID, staked amount, calculated hashrate, and the current block timestamp.

  6. Updating user's hashrate and reward debt: The user's total hashrate is updated based on the hashrate of the new order, and their reward debt (rewardDebt) is updated. This debt is the product of the user's hashrate and the accumulated rewards per share.

  7. Updating total hashrate and user's stake amount: The contract updates the total hashrate (totalPower) and the user's stake amount (amount) based on the hashrate and staked amount of the new order.

  8. Triggering Deposit event: Finally, the contract triggers a Deposit event, recording the user's address, product ID, and staked amount for on-chain tracking.

Profit Calculation

In the TokenFarm smart contract, calculating a user's profits involves several key steps and variables. Here is the detailed process of calculating user profits:

  1. Basis of Profit Calculation

    Accumulated Token Per Share (accTokenPerShare): This is a dynamically changing value that represents the accumulated token rewards per share (or per unit of LP tokens staked) since the start of mining. This value is updated with each new block and is linked to the total hashrate (totalPower).

  2. User Hashrate (power)

    The user's hashrate is calculated based on the amount of LP tokens they stake and the hashrate multiplier associated with the specific product. Hashrate determines the basis for calculating rewards for the user.

  3. User Reward Debt (rewardDebt)

    Each user's UserInfo structure includes a rewardDebt field, which records the user's accrued reward debt since the last update. This value is the product of the user's staked amount and accTokenPerShare.

    rewardDebt is a crucial concept used to track the amount of rewards allocated to each user relative to their staked liquidity. It dynamically updates and is essential for calculating the amount of token rewards a user should receive at any given time.

    rewardDebt is influenced by the user's staked hashrate and the duration of staking. When users stake funds in the Farming pool, their account records a rewardDebt. This value increases over time as rewards accumulate. When users remove funds from the pool or claim earnings, their rewardDebt is calculated against the current amount of reward tokens per liquidity unit in the pool to determine the amount of rewards they should receive.

    Specifically, rewardDebt is a snapshot value recorded when the user stakes, representing the portion of reward tokens each liquidity token should receive. When users wish to withdraw their rewards, the system calculates the difference between the current reward token amount per hashrate share (tracked by accTokenPerShare) and the user's rewardDebt. This difference is the user's unclaimed reward.

  4. Calculating Pending Rewards (pendingToken)

    The user's pending rewards are calculated using the pendingToken function, which considers the user's hashrate and the changes in accTokenPerShare since the last update.

  5. Updating and Claiming Rewards

    When users deposit (deposit) or claim rewards (_withdraw), the contract calls the updatePool function to update accTokenPerShare and lastRewardBlock.

    The user's rewardDebt is recalculated based on their current hashrate and the updated accTokenPerShare.

  6. Specific Calculation Steps

    • Updating Pool Rewards: multiplier = getMultiplier(lastRewardBlock, block.number); tokenReward = multiplier × tokenPerBlock; accTokenPerShare = accTokenPerShare + (tokenReward × 1e12 / totalPower);

    • Calculating User Pending Rewards: pending = (user.power × accTokenPerShare / 1e12) - user.rewardDebt;

    • Updating User Reward Debt: user.rewardDebt = user.power × accTokenPerShare / 1e12;

    • Claiming Rewards: Users claim rewards by calling the withdraw function, which triggers the _withdraw internal function. Their earnings balance increases by their pending rewards (pending). Their rewardDebt is updated to reflect the new reward status.

Example of Hashrate and Profit Calculation in the Contract

When the smart contract needs to calculate the reward generated per block, it updates accTokenPerShare using the following formula:: accTokenPerShare=accTokenPerShare+(tokenReward×1𝑒12totalPower)accTokenPerShare=accTokenPerShare+(totalPowertokenReward×1e12)accTokenPerShare=accTokenPerShare+(tokenReward×1𝑒12totalPower)accTokenPerShare=accTokenPerShare+(totalPowertokenReward×1e12​)

Where:

  • tokenReward is the token reward generated per block.

  • totalPower is the total hashrate of LP tokens staked by all users.

  • 1e12 is a precision factor used to maintain accuracy in calculations. It ensures that there is enough precision even when large amounts of tokens are distributed, and prevents exceeding the maximum value of the uint256 type.

At any point in time, the pending rewards for a user can be calculated as follows:pendingReward=(userPower×accTokenPerShare÷1𝑒12)userRewardDebtpendingReward=(userPower×accTokenPerShare÷1e12)userRewardDebtpendingReward=(userPower×accTokenPerShare÷1𝑒12)−userRewardDebtpendingReward=(userPower×accTokenPerShare÷1e12)−userRewardDebt

Where:

  • userPower is the current hashrate of the user.

  • accTokenPerShare is the accumulated reward per share.

  • userRewardDebt is the reward debt recorded for the user at the last reward claim.

Interface


//SPDX-License-Identifier: MIT
pragma solidity >=0.8.12;
pragma experimental ABIEncoderV2;

interface TokenFarm {
    // Product information structure
    struct Product {
        uint256 cycle; // Cycle timestamp
        uint256 multiplier; // Multiplier, 1 represents 1/1000
    }

    // Order information structure
    struct Order {
        uint256 id; // Order ID
        uint256 amount; // Participation amount
        uint256 power; // Order power
        uint256 orderTime; // Order time
    }

    // User information structure
    struct UserInfo {
        uint256 power; // User power
        uint256 amount; // User staked amount
        uint256 received; // User rewards received
        uint256 earnings; // User pending rewards
        uint256 rewardDebt; // User cumulative reward withdrawals
        uint256 rewardDividendDebt; // User cumulative dividend reward withdrawals
        Order[] orders; // User's current orders
        Order[] history; // User's historical orders
    }

    // Deposit event
    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    // Withdraw event
    event Withdraw(address indexed user, uint256 poolId, uint256 amount);

    // Initialization function
    function initialize(
        address _token,
        address _lpToken,
        uint256 _tokenPerBlock,
        uint256 _startBlock
    ) external;

    // Get reward token address
    function getToken() external view returns (address);

    // Get mining token address
    function getLpToken() external view returns (address);

    // Get reward start block
    function getStartBlock() external view returns (uint256);

    // Last reward update block
    function lastRewardBlock() external view returns (uint256);

    // Accumulated token per share
    function accTokenPerShare() external view returns (uint256);

    // Accumulated dividend per share
    function accDividendPerShare() external view returns (uint256);

    // Last dividend reward update block
    function lastDividendRewardBlock() external view returns (uint256);

    // Total power
    function totalPower() external view returns (uint256);

    // Total accumulated dividend funds
    function totalDividend() external view returns (uint256);

    // Token amount per block
    function getTokenPerBlock() external view returns (uint256);

    // Get user information
    function getUserInfo(address _user) external view returns (UserInfo memory);

    // Get weighted reward multiplier
    function getMultiplier(
        uint256 _from,
        uint256 _to
    ) external pure returns (uint256);

    // View pending token rewards
    // 0: Pool 1, 1: Pool 2
    function pendingToken(
        address _user
    ) external view returns (uint256, uint256);

    // Update pool rewards variables, update power
    function updatePool() external;

    /**
     * @dev Deposit LP tokens into the pool to earn rewards
     * @param _pid Product ID
     * @param _amount Amount of YT to participate
     */
    function deposit(uint256 _pid, uint256 _amount) external;

    /**
     * @dev User claims their rewards
     * @param _poolId Pool ID, 1 represents Pool 1, 2 represents Pool 2
     */
    function withdraw(uint256 _poolId) external;

    /**
     * @dev User claims their principal
     * @param _orderId Specific order ID, after claiming, the order is moved to the history
     */
    function harvestOrderPrincipal(uint256 _orderId) external;

    // Set the number of rewards per block
    function setTokenPerBlock(uint256 _tokenPerBlock) external;

    // Pause the contract
    function pause() external;

    // Resume the contract
    function unpause() external;

    // Add a new product, only callable by admin
    function addPro(uint256 _id, uint256 _cycle, uint256 _multiplier) external;

    // Get all products in the system
    function getAllProducts() external view returns (Product[] memory _products);

    /**
     * @dev Get aggregate information
     * @param _user User address
     * @return _balance User's LP assets
     * @return _allowance User's authorized amount for the farm contract
     * @return _pending1 Farm 1 earnings
     * @return _pending2 Farm 2 earnings (correcting the original comment)
     * @return _userPower User's power
     * @return _userAmount User's staked amount
     * @return _totalPower Total pool power
     * @return _totalAmount Total pool staked amount
     */
    function argInfo(
        address _user
    ) external view returns (
        uint256 _balance,
        uint256 _allowance,
        uint256 _pending1,
        uint256 _pending2, // Correcting the original comment error from _penfing2 to _pending2
        uint256 _userPower,
        uint256 _userAmount,
        uint256 _totalPower,
        uint256 _totalAmount
    );

    /**
     * @dev Get user's order information
     * @param _user User address
     * @return _orders User's current orders
     * @return _history User's historical orders
     */
    function getUserOrders(
        address _user
    ) external view returns (Order[] memory _orders, Order[] memory _history);

    /**
     * @dev Admin address, airdrop dividend rewards or external reward injections, directly distributed
     * This income is distributed in real-time
     */
    function intoFee(uint256 _amount) external;
}

Last updated