This document analyzes the implementation of TAIKO token delegation in Aerodrome liquidity pools to prevent "dead" governance tokens from accumulating in the DEX. The solution is elegantly simple: have pools delegate to a multisig once, and all future TAIKO deposits automatically contribute voting power.
When tokens are transferred, ERC20Votes moves voting power between the delegates of the sender and receiver:
// In _transferVotingUnits (simplified)
_moveDelegateVotes(delegates(from), delegates(to), amount)This means:
- If the receiver (Pool) has delegated to a multisig, ALL incoming tokens add to the multisig's voting power
- The sender's delegation status is irrelevant for the receiver's voting power
- Once delegated, the pool automatically captures voting power from all deposits
Add delegation to pool initialization for TAIKO pools:
function initialize(address _token0, address _token1, bool _stable) external {
// ... existing initialization ...
address TAIKO = 0x...; // TAIKO address
address MULTISIG = 0x...; // Governance multisig
// If this pool contains TAIKO, delegate all voting power to multisig
if (_token0 == TAIKO || _token1 == TAIKO) {
IERC20Votes(TAIKO).delegate(MULTISIG);
}
}Add a callable function for existing pools:
contract Pool is IPool, ERC20Permit, ReentrancyGuard {
// ... existing code ...
/// @notice Delegates all TAIKO voting power in this pool to governance
/// @dev Only needs to be called once per pool
function delegateTaiko() external {
address TAIKO = 0x...; // TAIKO address
address MULTISIG = 0x...; // Governance multisig
require(
token0 == TAIKO || token1 == TAIKO,
"Pool does not contain TAIKO"
);
// Check if already delegated to avoid wasted gas
if (IERC20Votes(TAIKO).delegates(address(this)) == MULTISIG) {
return;
}
IERC20Votes(TAIKO).delegate(MULTISIG);
emit TaikoDelegated(IERC20(TAIKO).balanceOf(address(this)));
}
event TaikoDelegated(uint256 currentBalance);
}- Pool Creation: Pool is created with TAIKO as one token
- Delegation: Pool calls
delegate(multisig)(once) - User Deposits:
- User A (never delegated) deposits 1000 TAIKO → Multisig gets +1000 voting power
- User B (self-delegated) deposits 500 TAIKO → Multisig gets +500 voting power
- User C (delegated elsewhere) deposits 200 TAIKO → Multisig gets +200 voting power
The _transferVotingUnits function in ERC20Votes:
// When User A transfers to Pool:
// - delegates(User A) = address(0) (never delegated)
// - delegates(Pool) = Multisig (Pool delegated)
// Result: _moveDelegateVotes(address(0), Multisig, amount)
// Multisig gains voting power!// PoolFactory.sol
address public taikoAddress;
address public taikoMultisig;
function createPool(address tokenA, address tokenB, bool stable) external returns (address pool) {
pool = super.createPool(tokenA, tokenB, stable);
// Auto-delegate for TAIKO pools
if (tokenA == taikoAddress || tokenB == taikoAddress) {
IPool(pool).delegateTaiko();
}
}
// Pool.sol
function delegateTaiko() external {
(address taiko, address multisig) = IPoolFactory(factory).getTaikoConfig();
require(token0 == taiko || token1 == taiko, "No TAIKO");
IERC20Votes(taiko).delegate(multisig);
}// Hardcode addresses if known at deployment
function initialize(address _token0, address _token1, bool _stable) external {
// ... existing initialization ...
if (_token0 == 0x... || _token1 == 0x...) { // TAIKO address
IERC20Votes(0x...).delegate(0x...); // delegate to multisig
}
}For existing pools, add a simple function:
function delegateTaiko() external {
IERC20Votes(0x...).delegate(0x...); // TAIKO to multisig
}- One-Time Setup: Delegate once per pool, works forever
- Automatic: All future deposits contribute voting power
- Gas Efficient: No per-deposit operations
- Simple: Minimal code changes required
- Effective: Captures 100% of pool voting power
While anyone can safely call delegation, you might want to restrict it:
modifier onlyGovernance() {
require(
msg.sender == IPoolFactory(factory).voter() ||
msg.sender == MULTISIG,
"Unauthorized"
);
_;
}
function delegateTaiko() external onlyGovernance {
// ... delegation logic
}Track delegation status:
function getTaikoDelegation() external view returns (address) {
address TAIKO = 0x...;
if (token0 == TAIKO || token1 == TAIKO) {
return IERC20Votes(TAIKO).delegates(address(this));
}
return address(0);
}The elegant property of ERC20Votes is that once a contract delegates, all received tokens automatically contribute voting power to its chosen delegate. This makes the solution surprisingly simple:
- One delegation per pool - Called once, works forever
- Automatic capture - All deposits contribute voting power
- No maintenance - Set and forget
Whether implemented at pool creation or retroactively, this approach effectively prevents "dead" governance tokens from accumulating in Aerodrome pools with minimal complexity.