Compound Governor Integration

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Compound Governor Integration
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Developing rage quit mechanism

Rage quit is a member's right to exit a DAO and claim proportional treasury share before proposal execution they disagree with. Mechanic came from Moloch DAO (2019) and solves fundamental governance problem: minority protection. Without rage quit, losing minority member can either accept decision or sell stake on market. With rage quit — third option: exit with fair asset share.

Moloch-style rage quit: classic implementation

Moloch V2/V3 — reference implementation. Participants hold shares (internal accounting, not ERC-20) and loot (shares without voting power). Rage quit converts shares/loot to proportional treasury token share.

contract MolochDAO {
    struct Member {
        address delegateKey;
        uint256 shares;      // voting stakes
        uint256 loot;        // non-voting stakes (rage quit without losing voting power)
        bool exists;
        uint256 highestIndexYesVote;  // for rage quit lock
        uint256 jailed;
    }
    
    mapping(address => Member) public members;
    uint256 public totalShares;
    uint256 public totalLoot;
    
    // Whitelist of approved tokens for rage quit
    address[] public approvedTokens;
    
    function ragequit(uint256 sharesToBurn, uint256 lootToBurn) public nonReentrant {
        require(members[msg.sender].shares >= sharesToBurn, "Insufficient shares");
        require(members[msg.sender].loot >= lootToBurn, "Insufficient loot");
        
        // Can't rage quit if voted YES on proposal in queue
        // (protection from "vote yes, drain treasury, rage quit" attack)
        require(
            canRagequit(members[msg.sender].highestIndexYesVote),
            "Cannot ragequit until highest index proposal member voted YES on is processed"
        );
        
        _ragequit(msg.sender, sharesToBurn, lootToBurn);
    }
    
    function _ragequit(address memberAddress, uint256 sharesToBurn, uint256 lootToBurn) internal {
        uint256 initialTotalSharesAndLoot = totalShares + totalLoot;
        
        members[memberAddress].shares -= sharesToBurn;
        members[memberAddress].loot -= lootToBurn;
        totalShares -= sharesToBurn;
        totalLoot -= lootToBurn;
        
        // Proportional payout across all approved tokens
        for (uint256 i = 0; i < approvedTokens.length; i++) {
            address token = approvedTokens[i];
            uint256 treasuryBalance = IERC20(token).balanceOf(address(this));
            
            uint256 amountToRagequit = (treasuryBalance * (sharesToBurn + lootToBurn)) 
                / initialTotalSharesAndLoot;
            
            if (amountToRagequit > 0) {
                IERC20(token).safeTransfer(memberAddress, amountToRagequit);
            }
        }
        
        emit Ragequit(memberAddress, sharesToBurn, lootToBurn);
    }
    
    function canRagequit(uint256 highestIndexYesVote) public view returns (bool) {
        // Proposal with this index must be processed
        require(highestIndexYesVote < proposalQueue.length, "No proposal queue");
        return proposals[proposalQueue[highestIndexYesVote]].processed;
    }
}

Key protection: highestIndexYesVote lock

Without this protection, attack is possible: vote YES on proposal giving ETH to attacker → treasury still full → rage quit → attacker gets both treasury share and proposal payout. Lock blocks rage quit while proposal not processed yet.

Nouns-style fork: protocol-level rage quit

Nouns V3 implements larger-scale rage quit — not one member exiting, but entire DAO fork. If enough token holders in escrow (e.g. 20%) — new fork DAO created with proportional treasury share.

contract NounsDAOForkEscrow {
    INounsToken public nounsToken;
    address public dao;
    uint256 public forkId;
    
    // tokenId => owner (for return if fork not activated)
    mapping(uint256 => address) private escrowedTokens;
    uint256 public numTokensInEscrow;
    
    function escrowToFork(
        uint256[] calldata tokenIds,
        uint256[] calldata proposalIds,
        string calldata reason
    ) external {
        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(
                nounsToken.ownerOf(tokenIds[i]) == msg.sender,
                "Not token owner"
            );
            escrowedTokens[tokenIds[i]] = msg.sender;
            nounsToken.transferFrom(msg.sender, address(this), tokenIds[i]);
        }
        
        numTokensInEscrow += tokenIds.length;
        emit TokensEscrowed(msg.sender, tokenIds, proposalIds, reason);
    }
    
    // Return tokens if changed mind before fork activation
    function returnTokensToOwner(address owner, uint256[] calldata tokenIds) external {
        require(msg.sender == dao, "Only DAO");
        
        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(escrowedTokens[tokenIds[i]] == owner, "Not escrow owner");
            escrowedTokens[tokenIds[i]] = address(0);
            nounsToken.transferFrom(address(this), owner, tokenIds[i]);
        }
        
        numTokensInEscrow -= tokenIds.length;
    }
}

After fork activation — new NounsToken deployed with copy of supply, treasury split proportionally:

function executeFork() external {
    // Only DAO can activate fork
    require(msg.sender == address(dao));
    
    uint256 forkSupply = forkEscrow.numTokensInEscrow();
    uint256 totalSupply = nounsToken.totalSupply();
    
    // Proportional treasury share
    uint256 ethToFork = (address(timelock).balance * forkSupply) / totalSupply;
    
    // USDC and other tokens too split
    for (uint256 i = 0; i < forkDAOTokens.length; i++) {
        uint256 tokenBalance = IERC20(forkDAOTokens[i]).balanceOf(address(timelock));
        uint256 tokenAmount = (tokenBalance * forkSupply) / totalSupply;
        // transfer to fork treasury
    }
    
    // Transfer ETH
    payable(forkTreasury).transfer(ethToFork);
    
    emit ForkExecuted(forkId, forkTreasury, ethToFork);
}

ERC-20 DAO with rage quit (Governor + Rage Quit)

For ERC-20 token DAOs rage quit is harder — tokens fungible and freely tradable. Mechanics must handle: should user lock tokens for rage quit participation or can exit with any balance?

Vault-based rage quit

Participants deposit tokens in vault (receive receipt tokens), vault participates in governance. Rage quit — burn receipt tokens, get proportional vault assets.

contract DAOVaultWithRageQuit {
    IERC20 public immutable governanceToken;
    // Receipt token — 1:1 with deposit
    IReceiptToken public immutable receiptToken;
    
    // Approved assets in vault (ETH + ERC-20)
    address[] public vaultAssets;
    
    // Pending proposals (for rage quit window)
    mapping(uint256 => uint256) public proposalRageQuitDeadline;
    
    function deposit(uint256 amount) external {
        governanceToken.safeTransferFrom(msg.sender, address(this), amount);
        receiptToken.mint(msg.sender, amount);
        emit Deposited(msg.sender, amount);
    }
    
    function rageQuit(
        uint256 receiptAmount,
        address[] calldata tokens  // which tokens to claim
    ) external {
        // Check no active proposals in grace period
        // that user voted on
        _validateNoActiveLocks(msg.sender);
        
        uint256 totalReceipts = receiptToken.totalSupply();
        
        receiptToken.burn(msg.sender, receiptAmount);
        
        // Proportional payout
        for (uint256 i = 0; i < tokens.length; i++) {
            uint256 balance;
            if (tokens[i] == address(0)) {
                balance = address(this).balance;
            } else {
                balance = IERC20(tokens[i]).balanceOf(address(this));
            }
            
            uint256 payout = (balance * receiptAmount) / totalReceipts;
            
            if (payout > 0) {
                if (tokens[i] == address(0)) {
                    payable(msg.sender).transfer(payout);
                } else {
                    IERC20(tokens[i]).safeTransfer(msg.sender, payout);
                }
            }
        }
        
        emit RageQuit(msg.sender, receiptAmount, tokens);
    }
    
    function _validateNoActiveLocks(address user) internal view {
        // Check no proposals in rage quit window
        // that user voted on
        uint256[] memory activeProposals = governor.getActiveProposals();
        for (uint256 i = 0; i < activeProposals.length; i++) {
            uint256 proposalId = activeProposals[i];
            if (governor.hasVoted(proposalId, user)) {
                uint256 deadline = proposalRageQuitDeadline[proposalId];
                require(block.timestamp > deadline, "Active proposal in rage quit window");
            }
        }
    }
}

Rage quit window: critical parameter

Rage quit window — period between proposal passing and execution during which dissenters can exit. Essentially Timelock with rage quit functionality.

Parameter Typical value Considerations
Rage quit window 3–7 days Should be enough for reaction
Voting period 3–7 days Informs about passed decisions
Execution delay Rage quit window + buffer Timelock >= rage quit window
Min stake to participate 0 (any holder) Or threshold to prevent spam

Typical mistake: Timelock shorter than rage quit window. If proposal executes in 24 hours but rage quit window — 3 days, mechanic doesn't work.

Gas optimization

Rage quit with large token list is expensive — iterating all assets. Optimizations:

  • Claim per token: user specifies which tokens to claim, not all
  • Merkle distribution: for many assets — snapshot + merkle tree
  • Pull payment pattern: funds reserved, user withdraws separately

When rage quit doesn't fit

Rage quit works for treasury DAOs (hold fungible assets). For operational DAOs (execute work) — harder: treasury may be illiquid assets (NFT, vesting tokens, LP positions). Rage quit on illiquid requires either immediate liquidation (losses) or delayed payouts.

In such cases consider token buyback mechanism or exit fee as alternative to direct rage quit.