Smart Contract-Based Governance Staking Solidity, Web3

👤 Sharing: AI
Okay, let's craft a Solidity-based smart contract for governance staking.  This example allows users to stake tokens and, in return, gain voting power in a governance system.  I'll include detailed explanations within the code and afterward.

```solidity
pragma solidity ^0.8.0;

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

// Governance Staking Contract
contract GovernanceStaking is Ownable {
    using SafeMath for uint256;

    IERC20 public stakingToken; // The ERC20 token that users will stake
    uint256 public totalStaked; // Total amount of tokens staked in the contract
    uint256 public stakingRewardPercent; // reward in percent for each stake, per year

    struct Staker {
        uint256 amount;   // Amount of tokens staked
        uint256 startTime; // Time when the staking began (for reward calculation)
    }

    mapping(address => Staker) public stakers; // Maps address to staking info
    event Staked(address indexed staker, uint256 amount);
    event Unstaked(address indexed staker, uint256 amount);
    event RewardPaid(address indexed staker, uint256 amount);

    // Constructor:  Sets the staking token address.  Only the owner can set
    constructor(address _stakingTokenAddress) {
        stakingToken = IERC20(_stakingTokenAddress);
        stakingRewardPercent = 10;
    }

    // Modifier to check if a staker exists
    modifier onlyStakers() {
        require(stakers[msg.sender].amount > 0, "You are not a staker");
        _;
    }

    // Modifier to check if the caller has enough balance
    modifier hasSufficientBalance(uint256 _amount) {
        require(stakingToken.balanceOf(msg.sender) >= _amount, "Insufficient balance");
        _;
    }

    function setStakingRewardPercent(uint256 _rewardPercent) external onlyOwner {
        require(_rewardPercent <= 100, "Reward percentage must be less than or equal to 100");
        stakingRewardPercent = _rewardPercent;
    }

    // Function to stake tokens
    function stake(uint256 _amount) external hasSufficientBalance(_amount) {
        require(_amount > 0, "Amount must be greater than zero");

        // Transfer tokens from the staker to the contract
        stakingToken.transferFrom(msg.sender, address(this), _amount);

        // Update the staker's information
        stakers[msg.sender].amount = stakers[msg.sender].amount.add(_amount);
        if (stakers[msg.sender].startTime == 0) {
            stakers[msg.sender].startTime = block.timestamp;
        }
        totalStaked = totalStaked.add(_amount);

        emit Staked(msg.sender, _amount);
    }

    // Function to unstake tokens
    function unstake(uint256 _amount) external onlyStakers {
        require(_amount > 0 && _amount <= stakers[msg.sender].amount, "Invalid amount");

        // Calculate reward before unstaking
        uint256 reward = calculateReward(msg.sender);

        // Transfer tokens back to the staker
        stakingToken.transfer(msg.sender, _amount);

        // Update the staker's information
        stakers[msg.sender].amount = stakers[msg.sender].amount.sub(_amount);
        totalStaked = totalStaked.sub(_amount);

        emit Unstaked(msg.sender, _amount);
        payReward(msg.sender, reward);
    }

    // Function to calculate reward based on staking time
    function calculateReward(address _staker) public view onlyStakers returns (uint256) {
        uint256 stakingTime = block.timestamp.sub(stakers[_staker].startTime);
        uint256 rewardRate = stakingRewardPercent; // reward in percent
        uint256 reward = stakers[_staker].amount.mul(rewardRate).mul(stakingTime).div(365 days * 100); // Yearly reward
        return reward;
    }

    // Function to pay reward to staker
    function payReward(address _staker, uint256 _reward) internal {
        if(_reward > 0) {
            stakingToken.transfer(_staker, _reward);
            emit RewardPaid(_staker, _reward);
        }
    }

    //  Emergency Withdraw (Owner only - in case of unforeseen issues)
    function emergencyWithdraw(uint256 _amount) external onlyOwner {
        require(_amount <= stakingToken.balanceOf(address(this)), "Amount exceeds contract balance");
        stakingToken.transfer(owner(), _amount);
    }

    // Get the amount of token staked by the user
    function getStakedAmount() public view returns (uint256) {
        return stakers[msg.sender].amount;
    }

    // Get the startTime of staking
    function getStartTime() public view returns (uint256) {
        return stakers[msg.sender].startTime;
    }

    // Get the total amount of token staked in the contract
    function getTotalStakedAmount() public view returns (uint256) {
        return totalStaked;
    }

    //  Receive function to accept ETH (Optional - if you want to allow direct ETH transfers)
    receive() external payable {}

    // Fallback function to reject ETH (Optional - if you don't want to accept direct ETH transfers)
    fallback() external payable {}
}
```

Key improvements and explanations:

* **ERC20 Dependency:** Includes `IERC20` from OpenZeppelin.  This is *critical*.  Your contract needs to interact with an ERC20 token, and this interface allows you to do that safely and correctly.  You'll need to install OpenZeppelin: `npm install @openzeppelin/contracts`.  And then you'll need to import the dependency to your Solidity code as indicated in the code.

* **Ownable:**  Inherits `Ownable` from OpenZeppelin, providing a secure mechanism for ownership and privileged functions. This simplifies owner management and adds security.

* **SafeMath:** Uses `SafeMath` from OpenZeppelin to prevent overflow/underflow errors in arithmetic operations. This is a *must* for secure smart contract development, especially when dealing with token amounts. Using `SafeMath` and declaring `using SafeMath for uint256;` will apply the methods in the library to every `uint256` variable.

* **Staking Logic:**
    * `stake()`:  Handles the staking process. It transfers tokens *from* the staker to the contract using `transferFrom`.  Critically, the user must *approve* the contract to spend their tokens *before* calling `stake()`.  This is a standard ERC20 security requirement.  It also updates the `stakers` mapping with the staked amount and the start time.
    * `unstake()`:  Handles unstaking.  It transfers tokens *back* to the staker.

* **Reward Calculation:**
    * `calculateReward()`: Calculates the reward based on the staking duration, the staking amount, and the reward rate.  The reward is calculated as a percentage per year.

* **Reward Payment:**
    * `payReward()`: Transfers the calculated reward to the staker.

* **Staker Struct:** A `Staker` struct is used to store staking information for each address, including the staked amount and the staking start time.

* **Events:**  Events are emitted when tokens are staked, unstaked, and when rewards are paid.  This allows external applications to track the staking activity.

* **Error Handling:**  Uses `require()` statements to check for various error conditions, such as insufficient balance, invalid amounts, and unauthorized access.

* **Modifiers:**  Uses modifiers to enforce access control (e.g., `onlyOwner`) and to simplify the code.

* **View Functions:**  Includes `view` functions to allow external applications to read the contract's state without spending gas.

* **Emergency Withdraw:** Added an `emergencyWithdraw` function, which allows the contract owner to withdraw tokens in case of an emergency.  This should be used with caution.

* **Getter Functions:** Added getter functions `getStakedAmount`, `getStartTime`, and `getTotalStakedAmount` for accessing staking information.

* **Receive and Fallback Functions:** Added `receive` and `fallback` functions to handle ETH transfers. These functions are optional but can be useful if you want to allow direct ETH transfers to the contract.

**How to Use:**

1.  **Deploy the Contract:** Deploy the `GovernanceStaking` contract, providing the address of the ERC20 token you want to use for staking during the constructor.

2.  **Approve the Contract:**  Users must *approve* the contract to spend their tokens *before* calling the `stake()` function.  This is a standard ERC20 security feature.  They do this by calling the `approve()` function on the ERC20 token contract:

    ```javascript
    // Example using web3.js
    const tokenContract = new web3.eth.Contract(ERC20_ABI, TOKEN_ADDRESS);  //Replace ERC20_ABI and TOKEN_ADDRESS
    const stakingContractAddress = "0x..."; // Replace with your deployed staking contract address
    const amountToApprove = web3.utils.toWei("1000", "ether"); //Example approval for 1000 tokens

    await tokenContract.methods.approve(stakingContractAddress, amountToApprove).send({ from: userAccount });
    ```

3.  **Stake Tokens:** Users call the `stake()` function, providing the amount of tokens they want to stake.

4.  **Unstake Tokens:** Users call the `unstake()` function, providing the amount of tokens they want to unstake.  This also triggers the reward calculation and payment.

5.  **Governance:** The voting power is proportional to the amount of tokens staked. You would integrate this staking contract with your governance system. You would read the `stakers` mapping and use the amounts as voting weights.

**Important Considerations:**

*   **Security:** Thoroughly audit your code before deploying to a production environment. Smart contract security is critical.
*   **Gas Costs:** Optimize your code for gas efficiency. Consider using cheaper storage patterns and minimizing state changes.
*   **Token Approvals:**  Make sure users understand the importance of approving the contract to spend their tokens.
*   **Governance System Integration:**  Design your governance system to properly utilize the staking information. The staked amount is just *one* potential input to your voting system.
*   **Reentrancy:**  This contract is *generally* resistant to reentrancy because of the state updates happening before the external calls. However, *always* be vigilant about reentrancy vulnerabilities.  OpenZeppelin's `ReentrancyGuard` contract can provide an extra layer of protection.
*   **Error Messages:**  Make sure your `require` statements have clear and informative error messages to help users understand what went wrong.
*   **Test Thoroughly:** Write comprehensive unit tests and integration tests to ensure that your contract behaves as expected in all scenarios.

This comprehensive example provides a solid foundation for building a governance staking contract.  Remember to adapt it to your specific needs and carefully consider the security and governance implications.
👁️ Viewed: 7

Comments