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