Smart Contracts
This section provides comprehensive documentation for the smart contracts powering Treem DAO governance.
Contract Architecture
Overview
The Treem DAO governance system consists of four main smart contracts working together to provide secure, transparent, and efficient governance:
graph TD
A[TreemGovernanceToken] --> B[TreemGovernor]
A --> C[TreemStaking]
B --> D[TimelockController]
C --> A
C --> B
Contract Relationships
TreemGovernanceToken: Provides voting power and token transfers
TreemStaking: Manages token locks and voting eligibility
TreemGovernor: Handles proposal creation and voting
TimelockController: Enforces execution delays for security
TreemGovernanceToken Contract
Contract Overview
Purpose: ERC-20 governance token with voting capabilitiesStandard: ERC-20, ERC-20Permit, ERC-20VotesFeatures: Voting power delegation, permit signatures, governance integration
Token Specifications
contract TreemGovernanceToken is ERC20, ERC20Permit, ERC20Votes, Ownable {
uint256 public constant TOTAL_SUPPLY = 1_000_000_000 * 10**18; // 1B tokens
string public constant AGREEMENT_IPFS = "bafybeiatjzzgloq7dfmytaeuxwnktquinjdxzasw555lfwey6mpnihbpta";
}
Distribution Allocation
Foundation Treasury
400,000,000 TREEM
40%
Operations & Development
Community Grants
300,000,000 TREEM
30%
Community Funding
Reserve Future
150,000,000 TREEM
15%
Strategic Reserves
Founders & Team
150,000,000 TREEM
15%
Team Allocation
Key Functions
Token Information
function name() external pure returns (string memory) {
return "Treem Governance Token";
}
function symbol() external pure returns (string memory) {
return "TREEM";
}
function decimals() external pure returns (uint8) {
return 18;
}
function totalSupply() external pure returns (uint256) {
return TOTAL_SUPPLY;
}
Voting Functions
function delegate(address delegatee) external {
// Delegate voting power to another address
}
function getVotes(address account) external view returns (uint256) {
// Get current voting power of account
}
function getPastVotes(address account, uint256 blockNumber) external view returns (uint256) {
// Get historical voting power at specific block
}
Agreement Access
function getAgreementIPFS() external pure returns (string memory) {
return AGREEMENT_IPFS;
}
function getAgreementURL() external pure returns (string memory) {
return "https://ipfs.io/ipfs/bafybeiatjzzgloq7dfmytaeuxwnktquinjdxzasw555lfwey6mpnihbpta";
}
Events
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
TreemStaking Contract
Contract Overview
Purpose: Manage token staking with lock periods and voting eligibilityFeatures: Tiered staking, voting rewards, grant proposal permissions
Staking Tiers
enum StakeTier {
NONE, // No active stake
SIX_MONTHS, // 6-month lock period
TWELVE_MONTHS // 12-month lock period
}
struct StakeInfo {
uint256 amount; // Staked token amount
uint256 startTime; // Stake creation timestamp
StakeTier tier; // Staking tier
}
Constructor
constructor(
address _treemToken, // TREEM token address
address _votesToken, // Same as TREEM token (IVotes interface)
address _treasury // Treasury for bonus payments
) {
treemToken = IERC20(_treemToken);
votesToken = IVotes(_votesToken);
treasury = _treasury;
bonusPerVote = 100 * 1e18; // 100 TREEM per vote
}
Core Functions
Staking Operations
function stake(uint256 amount, StakeTier tier) external {
require(tier == StakeTier.SIX_MONTHS || tier == StakeTier.TWELVE_MONTHS, "Invalid tier");
require(amount > 0, "Amount must be > 0");
require(stakes[msg.sender].amount == 0, "Already staked");
treemToken.transferFrom(msg.sender, address(this), amount);
stakes[msg.sender] = StakeInfo(amount, block.timestamp, tier);
votesToken.delegate(msg.sender); // Auto-delegate to self
emit Staked(msg.sender, amount, tier);
}
function unstake() external {
StakeInfo storage info = stakes[msg.sender];
require(info.amount > 0, "Nothing to unstake");
uint256 requiredTime = info.tier == StakeTier.SIX_MONTHS ? 180 days : 365 days;
require(block.timestamp >= info.startTime + requiredTime, "Lock period not over");
uint256 amount = info.amount;
delete stakes[msg.sender];
treemToken.transfer(msg.sender, amount);
emit Unstaked(msg.sender, amount);
}
Reward Management
function recordVoteParticipation(address voter) external onlyOwner {
votesParticipated[voter] += 1;
}
function claimBonus() external {
uint256 participation = votesParticipated[msg.sender];
require(participation > 0, "No participation recorded");
require(stakes[msg.sender].amount > 0, "Must be staked");
uint256 bonus = bonusPerVote * participation;
votesParticipated[msg.sender] = 0;
treemToken.transferFrom(treasury, msg.sender, bonus);
emit BonusClaimed(msg.sender, bonus);
}
Permission Checks
function canProposeGrants(address user) external view returns (bool) {
return stakes[user].tier == StakeTier.TWELVE_MONTHS;
}
function canVoteOnGrants(address user) external view returns (bool) {
return stakes[user].tier == StakeTier.TWELVE_MONTHS;
}
function canVoteOnGeneral(address user) external view returns (bool) {
return stakes[user].tier != StakeTier.NONE;
}
Administrative Functions
function setBonusPerVote(uint256 newBonus) external onlyOwner {
bonusPerVote = newBonus;
}
function setTreasury(address newTreasury) external onlyOwner {
require(newTreasury != address(0), "Zero address");
treasury = newTreasury;
}
Events
event Staked(address indexed user, uint256 amount, StakeTier tier);
event Unstaked(address indexed user, uint256 amount);
event BonusClaimed(address indexed user, uint256 bonusAmount);
TreemGovernor Contract
Contract Overview
Purpose: Main governance contract for proposals and votingStandard: OpenZeppelin Governor with extensionsFeatures: Proposal creation, voting, execution with timelock
Governance Parameters
constructor(IVotes _token, TimelockController _timelock)
Governor("TreemGovernor")
GovernorSettings(
7200, // 1 day voting delay
50400, // 7 days voting period
5_000_000e18 // 0.5% proposal threshold
)
GovernorVotes(_token)
GovernorVotesQuorumFraction(4) // 4% quorum
GovernorTimelockControl(_timelock)
{}
Core Functions
Proposal Creation
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public override returns (uint256) {
require(getVotes(msg.sender, block.number - 1) >= proposalThreshold(), "Below threshold");
return super.propose(targets, values, calldatas, description);
}
Voting Functions
function castVote(uint256 proposalId, uint8 support) public override returns (uint256) {
return super.castVote(proposalId, support);
}
function castVoteWithReason(
uint256 proposalId,
uint8 support,
string memory reason
) public override returns (uint256) {
return super.castVoteWithReason(proposalId, support, reason);
}
State Management
enum ProposalState {
Pending, // Proposal created, voting not started
Active, // Voting in progress
Canceled, // Proposal cancelled
Defeated, // Voting failed (no majority or quorum)
Succeeded, // Voting succeeded
Queued, // Successful proposal queued for execution
Expired, // Execution window expired
Executed // Proposal executed
}
function state(uint256 proposalId) public view override returns (ProposalState) {
return super.state(proposalId);
}
Proposal Execution
function execute(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public payable override returns (uint256) {
return super.execute(targets, values, calldatas, descriptionHash);
}
Events
event ProposalCreated(
uint256 proposalId,
address proposer,
address[] targets,
uint256[] values,
string[] signatures,
bytes[] calldatas,
uint256 startBlock,
uint256 endBlock,
string description
);
event VoteCast(
address indexed voter,
uint256 proposalId,
uint8 support,
uint256 weight,
string reason
);
event ProposalExecuted(uint256 proposalId);
TimelockController Contract
Overview
Purpose: Add execution delay for securityStandard: OpenZeppelin TimelockControllerFeatures: Multi-signature support, role-based access
Configuration
constructor(
uint256 minDelay, // 2 days minimum delay
address[] proposers, // Governor contract
address[] executors // Governor contract + admin
) TimelockController(minDelay, proposers, executors) {}
Key Functions
function schedule(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) external onlyRole(PROPOSER_ROLE);
function execute(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt
) external payable onlyRoleOrOpenRole(EXECUTOR_ROLE);
Deployment Guide
Prerequisites
Hardhat development environment
Ethereum wallet with ETH for deployment
Access to Ethereum node (Infura, Alchemy, etc.)
Deployment Sequence
1. Deploy TreemGovernanceToken
const TreemGovernanceToken = await ethers.getContractFactory("TreemGovernanceToken");
const token = await TreemGovernanceToken.deploy(
foundationTreasury, // Foundation treasury address
foundersTeam, // Founders team address
communityGrants, // Community grants address
reserveFuture, // Reserve future address
initialOwner // Initial owner (deployer)
);
await token.deployed();
2. Deploy TimelockController
const TimelockController = await ethers.getContractFactory("TimelockController");
const timelock = await TimelockController.deploy(
172800, // 2 days in seconds
[], // Proposers (will be set to governor)
[] // Executors (will be set to governor + admin)
);
await timelock.deployed();
3. Deploy TreemStaking
const TreemStaking = await ethers.getContractFactory("TreemStaking");
const staking = await TreemStaking.deploy(
token.address, // TREEM token address
token.address, // Votes token (same as TREEM)
treasury // Treasury address for rewards
);
await staking.deployed();
4. Deploy TreemGovernor
const TreemGovernor = await ethers.getContractFactory("TreemGovernor");
const governor = await TreemGovernor.deploy(
token.address, // Votes token
timelock.address // Timelock controller
);
await governor.deployed();
5. Configure Contracts
// Grant governor roles in timelock
await timelock.grantRole(await timelock.PROPOSER_ROLE(), governor.address);
await timelock.grantRole(await timelock.EXECUTOR_ROLE(), governor.address);
// Renounce admin role from deployer
await timelock.revokeRole(await timelock.TIMELOCK_ADMIN_ROLE(), deployer.address);
Gas Estimates
TreemGovernanceToken
~2,500,000
~0.125 ETH
TreemStaking
~1,200,000
~0.060 ETH
TreemGovernor
~3,000,000
~0.150 ETH
TimelockController
~1,500,000
~0.075 ETH
Total
~8,200,000
~0.410 ETH
Security Considerations
Access Controls
Critical Functions Protected:
Contract ownership transfer
Parameter updates
Emergency functions
Treasury management
Role-Based Security:
modifier onlyOwner() {
require(msg.sender == owner(), "Not the owner");
_;
}
modifier onlyRole(bytes32 role) {
require(hasRole(role, msg.sender), "Access denied");
_;
}
Reentrancy Protection
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract TreemStaking is ReentrancyGuard {
function stake(uint256 amount, StakeTier tier) external nonReentrant {
// Protected against reentrancy
}
}
Time-Based Security
Block Timestamp Usage:
Lock period calculations use
block.timestamp
Resistant to minor manipulation
Suitable for periods measured in days
Snapshot Mechanism:
Voting power calculated at proposal creation
Prevents vote manipulation
Historical data preserved
Integration Examples
Frontend Integration
Contract Instance Creation
import { ethers } from 'ethers';
// Contract addresses
const CONTRACTS = {
TREEM_TOKEN: '0x...',
STAKING: '0x...',
GOVERNOR: '0x...',
TIMELOCK: '0x...'
};
// Create contract instances
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const treemToken = new ethers.Contract(
CONTRACTS.TREEM_TOKEN,
TreemTokenABI,
signer
);
const stakingContract = new ethers.Contract(
CONTRACTS.STAKING,
TreemStakingABI,
signer
);
Common Operations
// Stake tokens
async function stakeTokens(amount: string, tier: number) {
const tx = await stakingContract.stake(
ethers.utils.parseEther(amount),
tier // 1 for SIX_MONTHS, 2 for TWELVE_MONTHS
);
await tx.wait();
}
// Check staking status
async function getStakeInfo(address: string) {
const stakeInfo = await stakingContract.stakes(address);
return {
amount: ethers.utils.formatEther(stakeInfo.amount),
startTime: new Date(stakeInfo.startTime.toNumber() * 1000),
tier: stakeInfo.tier
};
}
// Create proposal
async function createProposal(description: string) {
const tx = await governorContract.propose(
[], // targets
[], // values
[], // calldatas
description
);
await tx.wait();
}
Event Listening
// Listen for staking events
stakingContract.on('Staked', (user, amount, tier, event) => {
console.log(`User ${user} staked ${ethers.utils.formatEther(amount)} TREEM`);
});
// Listen for voting events
governorContract.on('VoteCast', (voter, proposalId, support, weight, reason, event) => {
console.log(`Vote cast: ${voter} voted ${support} with weight ${weight}`);
});
Troubleshooting
Common Deployment Issues
Insufficient Gas:
Error: Transaction ran out of gas
Solution: Increase gas limit to 3,000,000+
Constructor Arguments:
Error: Invalid constructor arguments
Solution: Verify all addresses are valid and checksummed
Network Configuration:
Error: Network mismatch
Solution: Ensure correct network configuration in hardhat.config.js
Runtime Issues
Staking Failures:
Check token approval
Verify sufficient balance
Ensure not already staked
Voting Issues:
Confirm tokens are staked
Check proposal is in active state
Verify voting power > 0
Proposal Creation:
Meet proposal threshold
Provide valid description
Check governor contract state
This comprehensive smart contracts documentation provides all necessary information for understanding, deploying, and interacting with the Treem DAO governance contracts.
Last updated
Was this helpful?