feat: introduce a protocol fee margin (AZIP-23) - #25260
Conversation
Adds a governance-set margin `mu` on the mana base fee. The fee becomes `cost * (1 + mu) * congestionMultiplier`: operators still receive exactly `cost` from the fee waterfall plus the unchanged block reward, and the markup above cost goes to a governance-set `protocolFeeRecipient`, which defaults to the current burn address. - `mu` is a uint16 bps field packed into bits 224-239 of `CompressedFeeConfig` and deploys at 0, so the rollup is bit-identical to today. - `setProtocolFeeMargin` is governance-only and rate-limited to x3/2 on the fee multiplier per 30-day window, matching `setProvingCostPerMana`. Decreases are immediate and unrestricted; setting the current value is a no-op. - The `(1 + mu)` scaling is applied only to the `fakeExponential` factor in `congestionMultiplier`. The `mulDiv` divisor stays `MINIMUM_CONGESTION_MULTIPLIER` -- scaling both sites cancels the margin. - The fee header's uint64 `congestionCost` field becomes `protocolFee` and carries `summedMinFee - sequencerCost - proverCost` as a single subtraction clamped at 0, so `fee - protocolFee = cost * manaUsed` holds to the wei. - `RewardLib.BURN_ADDRESS` becomes the deploy-time default for a stored `protocolFeeRecipient`; `getBurnAddress()` is replaced by `getProtocolFeeRecipient()`. - The TypeScript fee mirror is updated to match bit-exactly, with `mu` threaded through the L1 config read into the fee predictor.
| event ManaTargetUpdated(uint256 indexed manaTarget); | ||
| event PrunedPending(uint256 provenCheckpointNumber, uint256 pendingCheckpointNumber); | ||
| event ProtocolFeeMarginUpdated(uint16 oldBps, uint16 newBps); | ||
| event ProtocolFeeRecipientUpdated(address indexed oldRecipient, address indexed newRecipient); |
There was a problem hiding this comment.
I don't know if we need indexed here.
| uint256 manaTarget; | ||
| uint256 congestionUpdateFraction; | ||
| EthValue provingCostPerMana; | ||
| uint256 protocolFeeMarginBps; |
There was a problem hiding this comment.
should this be uint16?
| } | ||
|
|
||
| function getProtocolFeeMarginBps(CompressedFeeConfig _compressedFeeConfig) internal pure returns (uint256) { | ||
| return (CompressedFeeConfig.unwrap(_compressedFeeConfig) >> 224) & MASK_16_BITS; |
There was a problem hiding this comment.
Let's look at this carefully
| uint1 preHeat; | ||
| uint63 proverCost; Max value: 9.2233720369E18 | ||
| uint64 congestionCost; | ||
| uint64 protocolFee; |
There was a problem hiding this comment.
let's verify that this is both the congestionCost + protocolFeeMargin
| excessMana: uint256(excessMana), | ||
| ethPerFeeAsset: uint256(ethPerFeeAsset), | ||
| congestionCost: uint256(congestionCost), | ||
| protocolFee: uint256(protocolFee), |
There was a problem hiding this comment.
this is supposed to be \mu(sequencerCostPerMana + proverCostPerMana) + congestionCost
| RewardLib.updateConfig(_config); | ||
| } | ||
|
|
||
| function updateProtocolFeeMargin(uint16 _bps) external returns (bool changed, uint16 oldBps) { |
There was a problem hiding this comment.
needs to be gated so that callable by governance only. Here or in the FeeLib.sol
| return FeeLib.updateProtocolFeeMargin(_bps); | ||
| } | ||
|
|
||
| function updateProtocolFeeRecipient(address _recipient) external returns (address oldRecipient) { |
There was a problem hiding this comment.
needs to be gated so that callable by governance only. Here or in RewardLib.sol
| // such as sacrificial hearts, during rituals performed within temples. | ||
| // Deploy-time default for `protocolFeeRecipient`: the protocol fee is burned until governance | ||
| // redirects it via {updateProtocolFeeRecipient}. | ||
| address public constant BURN_ADDRESS = address(bytes20("CUAUHXICALLI")); |
There was a problem hiding this comment.
should we rename the BURN_ADDRESS constant?
Or we can remove this constant altogether and just set protocolFeeRecipient to address(bytes20("CUAUHXICALLI")) in initializeConfig
| /// @notice Owner-gated post-deployment writer for the protocol fee recipient. | ||
| /// @param _recipient The new recipient of the protocol fee tranche | ||
| /// @return oldRecipient The recipient in effect before this call | ||
| function updateProtocolFeeRecipient(address _recipient) internal returns (address oldRecipient) { |
There was a problem hiding this comment.
the event is not emitted from here, where is it emitted from? should it be emitted from here instead?
There was a problem hiding this comment.
okay it's emitted in the next file
Adds a governance-set margin
muon the mana base fee. The fee becomescost * (1 + mu) * congestionMultiplier: operators still receive exactlycostfrom the fee waterfall plus the unchanged block inflation, and the markup above cost goes to a governance-setprotocolFeeRecipient, which defaults to the current burn address.muis a uint16 bps field packed into bits 224-239 ofCompressedFeeConfigand deploys at 0, so the rollup is bit-identical to today.setProtocolFeeMarginis governance-only and rate-limited to x3/2 on the fee multiplier per 30-day window, matchingsetProvingCostPerMana. Decreases are immediate and unrestricted; setting the current value is a no-op.(1 + mu)scaling is applied only to thefakeExponentialfactor incongestionMultiplier. ThemulDivdivisor staysMINIMUM_CONGESTION_MULTIPLIER-- scaling both sites cancels the margin.congestionCostfield becomesprotocolFeeand carriessummedMinFee - sequencerCost - proverCostas a single subtraction clamped at 0, sofee - protocolFee = cost * manaUsedholds to the wei.RewardLib.BURN_ADDRESSbecomes the deploy-time default for a storedprotocolFeeRecipient;getBurnAddress()is replaced bygetProtocolFeeRecipient().muthreaded through the L1 config read into the fee predictor.