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

Recipient
Amount
Percentage
Purpose

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

Contract
Deployment Gas
Mainnet Cost (50 gwei)

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?