Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions foundry.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"lib/forge-std": {
"rev": "77041d2ce690e692d6e03cc812b57d1ddaa4d505"
},
"lib/govkit": {
"rev": "bd521852d54cb2b9119c1c9bf675e286f521b8fc"
},
"lib/murky": {
"rev": "5feccd1253d7da820f7cccccdedf64471025455d"
},
Expand Down
62 changes: 62 additions & 0 deletions script/proposal-7/Description.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.29;

string constant PART_TWO_DESCRIPTION = "## Summary\n" "\n"
"This proposal completes the v4 protocol fee rollout described in the [v4 fee activation temp check](https://gov.uniswap.org/t/temp-check-activate-v4-protocol-fees/26162), following the UNIfication fee rollout (proposals [#93](https://vote.uniswapfoundation.org/proposals/93), [#94](https://vote.uniswapfoundation.org/proposals/94), [#95](https://vote.uniswapfoundation.org/proposals/95), and [#96](https://vote.uniswapfoundation.org/proposals/96)). It uses the expedited governance process where fee parameter update proposals go directly to a five-day Snapshot followed by an onchain vote.\n"
"\n"
"Part 1 activated v4 protocol fees on Ethereum, Arbitrum, Base, BNB Chain, Polygon, OP Mainnet, and Robinhood Chain. Because of GovernorBravo's limit of 10 actions per proposal, the remaining chains were deferred to this vote. This proposal activates v4 fees on those five chains: Celo, Soneium, Worldchain, X Layer, and Zora.\n"
"\n"
"These chains activate with the newest fee policy configuration, which includes everything from part 1 plus a set of self-service **fee tier families** and a **native-math opt-in** described below. A companion proposal upgrades the part 1 chains to this same configuration.\n"
"\n" "---\n" "\n" "## Implementation Details\n" "\n"
"The V4 Fee Controller system (V4FeeAdapter + V4FeePolicy) is described in the part 1 proposal: governance sets a small number of rules, and the policy computes the fee for any pool on demand. Pools are sorted into families - by governance assignment, by flags the hook self-reports, or by pool characteristics - and each family carries its own fee logic.\n"
"\n"
"On these five chains, fees activate with the same family configuration approved in part 1 - the static-pool fee curve, CCA/LBP hooks opted into that curve, and the aggregator hook family - plus two additions:\n"
"\n"
"* **Fee tier families.** Twelve families with flat protocol fees from 0.1bp to 10bp (the PoolManager's cap), listed in the appendix. A hook that does not fit an existing family can opt its pools into a tier immediately by self-reporting the tier's flag, without waiting for a governance vote. Governance can always override any hook's family assignment by vote. A hook that signals multiple tiers is assigned the highest.\n"
"* **Native-math opt-in.** A dedicated flag lets a hook whose pools use the PoolManager's native swap math opt into the same fee curve that applies to hookless pools.\n"
"\n"
"**This proposal does not enable the protocol fee for any pools other than those in the families mentioned above.**\n"
"\n"
"Fees will flow to the TokenJar on each chain. UNI burned on L2s and alt-L1s will be bridged back to Ethereum mainnet and sent to `0xdead`.\n"
"\n" "---\n" "\n" "## Onchain Proposal Spec\n" "\n"
"**Pre-proposal** (to be completed by Uniswap Labs prior to an onchain vote)\n" "\n"
"* Deploy V4FeeAdapter and V4FeePolicy contracts on Celo, Soneium, Worldchain, X Layer, and Zora\n"
"* Configure the V4FeePolicy contracts with the native math protocol fee curve, CCA hook and aggregator hook family fee logic approved in part 1, and the fee tier families and native-math opt-in described above\n"
"* Transfer ownership of both contracts to governance, verified by the deployment scripts before the vote\n"
"\n"
"These contracts can be found [here](https://github.com/Uniswap/protocol-fees/tree/main/src/feeAdapters), and this post will be updated with addresses and explorer links when they have been deployed.\n"
"\n" "**In this proposal** (executed if the vote passes):\n" "\n"
"* Set the `V4FeeAdapter` as the `ProtocolFeeController` on the PoolManager on each chain (five actions, each a cross-chain governance message)\n"
"\n" "---\n" "\n" "## Appendix - Fee Tier Families\n" "\n"
"Fees are flat per family and expressed the same way as the static fee curve in part 1. The family ID doubles as the flag bit a hook self-reports to opt in.\n"
"\n" "| *Family ID / Flag Bit* | *Protocol Fee (bps)* |\n" "| :---- | :---- |\n" "| 20 | 0.1 |\n"
"| 21 | 0.5 |\n" "| 22 | 1 |\n" "| 23 | 2 |\n" "| 24 | 3 |\n" "| 25 | 4 |\n" "| 26 | 5 |\n"
"| 27 | 6 |\n" "| 28 | 7 |\n" "| 29 | 8 |\n" "| 30 | 9 |\n" "| 31 | 10 |\n";

string constant FAMILY_UPGRADE_DESCRIPTION = "## Summary\n" "\n"
"This proposal extends the v4 protocol fee system activated by the part 1 vote (Ethereum, Arbitrum, Base, BNB Chain, Polygon, OP Mainnet, and Robinhood Chain) with self-service **fee tier families** and a **native-math opt-in**. It uses the expedited governance process where fee parameter update proposals go directly to a five-day Snapshot followed by an onchain vote.\n"
"\n"
"Today, a hook that wants its pools to pay protocol fees but does not fit one of the approved families (static curve, CCA, aggregator) has no path other than a bespoke governance vote. This proposal adds twelve pre-approved fee tiers, from 0.1bp to 10bp, that any hook can opt into immediately by self-reporting a flag - no vote required. Governance keeps the last word: it can override any hook's family assignment at any time.\n"
"\n"
"**This proposal does not change any fee currently being charged.** Every pool paying fees today pays exactly the same fee after execution; the vote only adds the new opt-in families.\n"
"\n" "---\n" "\n" "## Implementation Details\n" "\n"
"* **Fee tier families.** Twelve families with flat protocol fees, listed in the appendix. A hook opts in by self-reporting the tier's flag via the fee classification interface. A hook that signals multiple tiers is assigned the highest.\n"
"* **Native-math opt-in.** A dedicated flag lets a hook whose pools use the PoolManager's native swap math opt into the same fee curve that applies to hookless pools.\n"
"\n"
"**Why a controller swap.** The V4 Fee Controller system was designed so its fee logic can be replaced wholesale rather than reconfigured piecemeal. Instead of sending many configuration calls through cross-chain governance messages, Uniswap Labs deploys a freshly configured V4FeeAdapter and V4FeePolicy pair on each chain ahead of the vote. The deployment scripts assert onchain that the new policy reproduces the live policy's fee outcomes exactly - same curve, same aggregator fees, same hook assignments - before ownership is transferred to governance. The proposal itself then executes a single action per chain: pointing the PoolManager at the new controller. The previous controller keeps serving fees until the moment of execution, so the swap is atomic.\n"
"\n"
"The new contracts also add batch admin functions (batch pool overrides and batch family defaults), making future fee votes cheaper to construct and easier to review.\n"
"\n" "Fees continue to flow to the TokenJar on each chain, and the UNI burn path is unchanged.\n"
"\n" "---\n" "\n" "## Onchain Proposal Spec\n" "\n"
"**Pre-proposal** (to be completed by Uniswap Labs prior to an onchain vote)\n" "\n"
"* Deploy V4FeeAdapter and V4FeePolicy contracts on Ethereum, Arbitrum, Base, BNB Chain, Polygon, OP Mainnet, and Robinhood Chain\n"
"* Configure the V4FeePolicy contracts identically to the live policies, plus the fee tier families and native-math opt-in described above, with parity verified onchain by the deployment scripts\n"
"* Transfer ownership of both contracts to governance\n" "\n"
"These contracts can be found [here](https://github.com/Uniswap/protocol-fees/tree/main/src/feeAdapters), and this post will be updated with addresses and explorer links when they have been deployed.\n"
"\n" "**In this proposal** (executed if the vote passes):\n" "\n"
"* Set the new `V4FeeAdapter` as the `ProtocolFeeController` on the PoolManager on each chain (seven actions: one direct call on Ethereum and six cross-chain governance messages)\n"
"\n" "---\n" "\n" "## Appendix - Fee Tier Families\n" "\n"
"Fees are flat per family and expressed the same way as the static fee curve in part 1. The family ID doubles as the flag bit a hook self-reports to opt in.\n"
"\n" "| *Family ID / Flag Bit* | *Protocol Fee (bps)* |\n" "| :---- | :---- |\n" "| 20 | 0.1 |\n"
"| 21 | 0.5 |\n" "| 22 | 1 |\n" "| 23 | 2 |\n" "| 24 | 3 |\n" "| 25 | 4 |\n" "| 26 | 5 |\n"
"| 27 | 6 |\n" "| 28 | 7 |\n" "| 29 | 8 |\n" "| 30 | 9 |\n" "| 31 | 10 |\n";
82 changes: 82 additions & 0 deletions script/proposal-7/FamilyConstants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: AGPl-3.0-only
pragma solidity 0.8.29;

import {FlagRule, FamilyDefaultAssignment} from "../../src/interfaces/IV4FeePolicy.sol";

// ─── Existing families (unchanged from proposal-6) ───

uint8 constant AGG_HOOK_ID = 11;
uint256 constant AGG_HOOK_FAMILY = 1 << AGG_HOOK_ID;

// ─── Native math self-opt-in ───

/// @dev Flag bit 255 lets a hook self-opt into the native-math fee schedule (fee buckets
/// keyed on the pool's static LP fee). Family ID 255 is V4FeePolicy.NATIVE_MATH_FAMILY_ID;
/// dynamic-fee pools that opt in fall through to defaultFee (the policy's dynamic-fee guard).
uint8 constant NATIVE_MATH_ID = 255;
uint256 constant NATIVE_MATH_OPT_IN_FLAG = 1 << NATIVE_MATH_ID;

/// @title FamilyTiers
/// @notice Fixed protocol-fee tier families hooks can self-opt into via protocolFeeFlags().
/// @dev Convention: family ID == flag bit index (matches the aggregator family at bit 11).
/// Tiers occupy IDs/bits 20-31, ascending by fee.
///
/// | Family ID | Flag Bit | Fee (pips/direction) |
/// | --------- | --------- | -------------------- |
/// | `20` | `1 << 20` | 10 |
/// | `21` | `1 << 21` | 50 |
/// | `22` | `1 << 22` | 100 |
/// | `23` | `1 << 23` | 200 |
/// | `24` | `1 << 24` | 300 |
/// | `25` | `1 << 25` | 400 |
/// | `26` | `1 << 26` | 500 |
/// | `27` | `1 << 27` | 600 |
/// | `28` | `1 << 28` | 700 |
/// | `29` | `1 << 29` | 800 |
/// | `30` | `1 << 30` | 900 |
/// | `31` | `1 << 31` | 1000 (cap) |
library FamilyTiers {
uint8 internal constant FIRST_TIER_ID = 20;
uint256 internal constant TIER_COUNT = 12;

/// @dev Per-direction protocol fee in pips (1 pip = 0.0001%) for each tier, ascending.
/// 1000 = ProtocolFeeLibrary.MAX_PROTOCOL_FEE (0.1% per direction).
function feePips() internal pure returns (uint24[12] memory) {
return [uint24(10), 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000];
}

/// @dev Family ID (== flag bit index) for tier `index`.
function tierId(uint256 index) internal pure returns (uint8) {
return uint8(FIRST_TIER_ID + index);
}

/// @dev Full flag rule set in priority order. First matching rule wins, so: aggregator
/// keeps its existing priority, tiers run descending (a hook signaling multiple tiers
/// pays the highest — misconfiguration must not underpay; governance can correct down),
/// and the native-math opt-in is last so an explicit tier choice beats it. All rules are
/// single-bit, so the descending-popcount ordering setFlagRules enforces holds trivially.
function flagRules() internal pure returns (FlagRule[] memory rules) {
rules = new FlagRule[](2 + TIER_COUNT);
rules[0] = FlagRule({requiredFlags: AGG_HOOK_FAMILY, familyId: AGG_HOOK_ID});
for (uint256 i; i < TIER_COUNT; ++i) {
uint8 id = tierId(TIER_COUNT - 1 - i);
rules[1 + i] = FlagRule({requiredFlags: uint256(1) << id, familyId: id});
}
rules[1 + TIER_COUNT] =
FlagRule({requiredFlags: NATIVE_MATH_OPT_IN_FLAG, familyId: NATIVE_MATH_ID});
}

/// @dev Family default assignments for all tiers (sentinel-encoded two-direction fee).
function familyDefaults() internal pure returns (FamilyDefaultAssignment[] memory assignments) {
uint24[12] memory fees = feePips();
assignments = new FamilyDefaultAssignment[](TIER_COUNT);
for (uint256 i; i < TIER_COUNT; ++i) {
assignments[i] = FamilyDefaultAssignment({familyId: tierId(i), feeValue: encodeFee(fees[i])});
}
}

/// @dev Handles v4 fee two way encoding.
function encodeFee(uint24 fee) internal pure returns (uint24) {
return fee << 12 | fee;
}
}
Loading
Loading