From e21c2b13783869eed0153e6fda9e50f4772a9240 Mon Sep 17 00:00:00 2001 From: geoknee Date: Thu, 21 Mar 2024 16:45:24 +0000 Subject: [PATCH 01/16] Add RevenueSharer L2 contract with some TODO comments --- .../src/L2/RevenueSharer.sol | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 packages/contracts-bedrock/src/L2/RevenueSharer.sol diff --git a/packages/contracts-bedrock/src/L2/RevenueSharer.sol b/packages/contracts-bedrock/src/L2/RevenueSharer.sol new file mode 100644 index 000000000000..2bf4be95bed5 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/RevenueSharer.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT +// TODO gk: this has been largely copy pasted from +// https://github.com/base-org/contracts/blob/main/src/revenue-share/FeeDisburser.sol +pragma solidity 0.8.15; + +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; + +import { L2StandardBridge } from "src/L2/L2StandardBridge.sol"; +import { FeeVault } from "src/universal/FeeVault.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { SafeCall } from "src/libraries/SafeCall.sol"; + +/** + * @title RevenueSharer + * @dev Withdraws funds from system FeeVault contracts, + * pays a share of revenue to Optimism + * and sends the remainder to a configurable adddress on L1. + */ +contract RevenueSharer { + /*////////////////////////////////////////////////////////////// + Constants + //////////////////////////////////////////////////////////////*/ + /** + * @dev The basis point scale which revenue share splits are denominated in. + */ + uint32 public constant BASIS_POINT_SCALE = 10_000; + /** + * @dev The minimum gas limit for the FeeDisburser withdrawal transaction to L1. + */ + uint32 public constant WITHDRAWAL_MIN_GAS = 35_000; + /** + * @dev The net revenue percentage denominated in basis points that is used in + * Optimism revenue share calculation. + */ + uint256 public constant OPTIMISM_NET_REVENUE_SHARE_BASIS_POINTS = 1_500; + /** + * @dev The gross revenue percentage denominated in basis points that is used in + * Optimism revenue share calculation. + */ + uint256 public constant OPTIMISM_GROSS_REVENUE_SHARE_BASIS_POINTS = 250; + + /*////////////////////////////////////////////////////////////// + Immutables + //////////////////////////////////////////////////////////////*/ + /** + * @dev The address of the Optimism wallet that will receive Optimism's revenue share. + */ + address payable public immutable OPTIMISM_WALLET; + /** + * @dev The address of the L1 wallet that will receive the OP chain runner's share of fees. + */ + address public immutable L1_WALLET; + /** + * @dev The minimum amount of time in seconds that must pass between fee disbursals. + */ + uint256 public immutable FEE_DISBURSEMENT_INTERVAL; + + /*////////////////////////////////////////////////////////////// + Variables + //////////////////////////////////////////////////////////////*/ + /** + * @dev The timestamp of the last disbursal. + */ + uint256 public lastDisbursementTime; + /** + * @dev Tracks aggregate net fee revenue which is the sum of sequencer and base fees. + * @dev Explicity tracking Net Revenue is required to seperate L1FeeVault initiated + * withdrawals from Net Revenue calculations. + */ + uint256 public netFeeRevenue; + + /*////////////////////////////////////////////////////////////// + Events + //////////////////////////////////////////////////////////////*/ + /** + * @dev Emitted when fees are disbursed. + * @param _disbursementTime The time of the disbursement. + * @param _paidToOptimism The amount of fees disbursed to Optimism. + * @param _totalFeesDisbursed The total amount of fees disbursed. + */ + event FeesDisbursed(uint256 _disbursementTime, uint256 _paidToOptimism, uint256 _totalFeesDisbursed); + /** + * @dev Emitted when fees are received from FeeVaults. + * @param _sender The FeeVault that sent the fees. + * @param _amount The amount of fees received. + */ + event FeesReceived(address indexed _sender, uint256 _amount); + /** + * @dev Emitted when no fees are collected from FeeVaults at time of disbursement. + */ + event NoFeesCollected(); + + /*////////////////////////////////////////////////////////////// + Constructor + //////////////////////////////////////////////////////////////*/ + /** + * @dev Constructor for the FeeDisburser contract which validates and sets immutable variables. + * @param _optimismWallet The address which receives Optimism's revenue share. + * @param _l1Wallet The L1 address which receives the remainder of the revenue. + * @param _feeDisbursementInterval The minimum amount of time in seconds that must pass between fee disbursals. + */ + constructor(address payable _optimismWallet, address _l1Wallet, uint256 _feeDisbursementInterval) { + require(_optimismWallet != address(0), "FeeDisburser: OptimismWallet cannot be address(0)"); + require(_l1Wallet != address(0), "FeeDisburser: L1Wallet cannot be address(0)"); + require( + _feeDisbursementInterval >= 24 hours, "FeeDisburser: FeeDisbursementInterval cannot be less than 24 hours" + ); + + OPTIMISM_WALLET = _optimismWallet; + L1_WALLET = _l1Wallet; + FEE_DISBURSEMENT_INTERVAL = _feeDisbursementInterval; + } + + /*////////////////////////////////////////////////////////////// + External Functions + //////////////////////////////////////////////////////////////*/ + /** + * @dev Withdraws funds from FeeVaults, sends Optimism their revenue share, and withdraws remaining funds to L1. + * @dev Implements revenue share business logic as follows: + * Net Revenue = sequencer FeeVault fee revenue + base FeeVault fee revenue + * Gross Revenue = Net Revenue + l1 FeeVault fee revenue + * Optimism Revenue Share = Maximum of 15% of Net Revenue and 2.5% of Gross Revenue + * L1 Wallet Revenue Share = Gross Revenue - Optimism Revenue Share + */ + function disburseFees() external virtual { + require( + block.timestamp >= lastDisbursementTime + FEE_DISBURSEMENT_INTERVAL, + "FeeDisburser: Disbursement interval not reached" + ); + + // Sequencer and base FeeVaults will withdraw fees to the FeeDisburser contract mutating netFeeRevenue + feeVaultWithdrawal(payable(Predeploys.SEQUENCER_FEE_WALLET)); + feeVaultWithdrawal(payable(Predeploys.BASE_FEE_VAULT)); + + feeVaultWithdrawal(payable(Predeploys.L1_FEE_VAULT)); + + // Gross revenue is the sum of all fees + uint256 feeBalance = address(this).balance; + + // TODO gk: etFeeRevenue = feeBalance - l1fees . Be clearer to just subtract this off, and better anticipates + // future improvements + // where we may be tracking the actual expenditure on L1 + + // Stop execution if no fees were collected + if (feeBalance == 0) { + emit NoFeesCollected(); + return; + } + + lastDisbursementTime = block.timestamp; + + // Net revenue is the sum of sequencer fees and base fees + uint256 optimismNetRevenueShare = netFeeRevenue * OPTIMISM_NET_REVENUE_SHARE_BASIS_POINTS / BASIS_POINT_SCALE; + netFeeRevenue = 0; + + uint256 optimismGrossRevenueShare = feeBalance * OPTIMISM_GROSS_REVENUE_SHARE_BASIS_POINTS / BASIS_POINT_SCALE; + + // Optimism's revenue share is the maximum of net and gross revenue + // TODO gk I think the wording can be improved here // Optimism's revenue share is the maximum of the + // respective shares of the net and gross revenue + uint256 optimismRevenueShare = Math.max(optimismNetRevenueShare, optimismGrossRevenueShare); + + // Send Optimism their revenue share on L2 + require( + SafeCall.send(OPTIMISM_WALLET, gasleft(), optimismRevenueShare), + "FeeDisburser: Failed to send funds to Optimism" + ); + + // Send remaining funds to L1 wallet on L1 + L2StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)).bridgeETHTo{ value: address(this).balance }( + L1_WALLET, WITHDRAWAL_MIN_GAS, bytes("") + ); + emit FeesDisbursed(lastDisbursementTime, optimismRevenueShare, feeBalance); + } + + /** + * @dev Receives ETH fees withdrawn from L2 FeeVaults. + * @dev Will revert if ETH is not sent from L2 FeeVaults. + */ + receive() external payable virtual { + if (msg.sender == Predeploys.SEQUENCER_FEE_WALLET || msg.sender == Predeploys.BASE_FEE_VAULT) { + // Adds value received to net fee revenue if the sender is the sequencer or base FeeVault + netFeeRevenue += msg.value; // TODO GK: be better to check balance before and after each FeeVault.withdraw() + // and explicitly label each chunk of ETH pulled in. The tracking of the fees from each vault is hard to + // reason about as is. + } else if (msg.sender != Predeploys.L1_FEE_VAULT) { + revert("FeeDisburser: Only FeeVaults can send ETH to FeeDisburser"); + } + emit FeesReceived(msg.sender, msg.value); + } + + /*////////////////////////////////////////////////////////////// + Internal Functions + //////////////////////////////////////////////////////////////*/ + /** + * @dev Withdraws fees from a FeeVault. + * @param _feeVault The address of the FeeVault to withdraw from. + * @dev Withdrawal will only occur if the given FeeVault's balance is greater than or equal to + * the minimum withdrawal amount. + */ + function feeVaultWithdrawal(address payable _feeVault) internal { + require( + FeeVault(_feeVault).WITHDRAWAL_NETWORK() == FeeVault.WithdrawalNetwork.L2, + "FeeDisburser: FeeVault must withdraw to L2" + ); + require( + FeeVault(_feeVault).RECIPIENT() == address(this), + "FeeDisburser: FeeVault must withdraw to FeeDisburser contract" + ); + if (_feeVault.balance >= FeeVault(_feeVault).MIN_WITHDRAWAL_AMOUNT()) { + FeeVault(_feeVault).withdraw(); + } + } +} From c958fa230dcdd934d9648155437b4807cc4ed8a5 Mon Sep 17 00:00:00 2001 From: geoknee Date: Thu, 21 Mar 2024 17:51:22 +0000 Subject: [PATCH 02/16] make RevenueSharer a predeploy --- packages/contracts-bedrock/src/L2/RevenueSharer.sol | 12 ++++++------ .../contracts-bedrock/src/libraries/Predeploys.sol | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/contracts-bedrock/src/L2/RevenueSharer.sol b/packages/contracts-bedrock/src/L2/RevenueSharer.sol index 2bf4be95bed5..63606a4f4f35 100644 --- a/packages/contracts-bedrock/src/L2/RevenueSharer.sol +++ b/packages/contracts-bedrock/src/L2/RevenueSharer.sol @@ -10,12 +10,12 @@ import { FeeVault } from "src/universal/FeeVault.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; -/** - * @title RevenueSharer - * @dev Withdraws funds from system FeeVault contracts, - * pays a share of revenue to Optimism - * and sends the remainder to a configurable adddress on L1. - */ +/// @custom:proxied +/// @custom:predeploy 0x4200000000000000000000000000000000000022 +/// @title RevenueSharer +/// @dev Withdraws funds from system FeeVault contracts, +/// pays a share of revenue to Optimism +/// and sends the remainder to a configurable adddress on L1. contract RevenueSharer { /*////////////////////////////////////////////////////////////// Constants diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index 0fc2d3935caa..74a7362f6ad9 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -8,6 +8,9 @@ library Predeploys { /// @notice Number of predeploy-namespace addresses reserved for protocol usage. uint256 internal constant PREDEPLOY_COUNT = 2048; + /// @notice Address of the RevenueSharer predeploy. + address internal constant REVENUE_SHARER = 0x4200000000000000000000000000000000000022; + /// @custom:legacy /// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated /// L2ToL1MessagePasser contract instead. From e8e8eaa069e290450c69990c6bdeda7b2bb76e51 Mon Sep 17 00:00:00 2001 From: geoknee Date: Fri, 22 Mar 2024 16:37:31 +0000 Subject: [PATCH 03/16] WIP mostly following https://github.com/ethereum-optimism/optimism/pull/9839/files#diff-91b2122803f6a5d39a9c95ef8c90ee82ef8d14647ddb31617b33cd312237f943 --- op-bindings/predeploys/addresses.go | 3 +++ packages/contracts-bedrock/scripts/Artifacts.s.sol | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/op-bindings/predeploys/addresses.go b/op-bindings/predeploys/addresses.go index 6d1b75e3bf4c..11b909273135 100644 --- a/op-bindings/predeploys/addresses.go +++ b/op-bindings/predeploys/addresses.go @@ -6,6 +6,7 @@ import "github.com/ethereum/go-ethereum/common" // This needs to be kept in sync with @eth-optimism/contracts-ts/wagmi.config.ts which also specifies this // To improve robustness and maintainability contracts-bedrock should export all addresses const ( + RevenueSharer = "0x4200000000000000000000000000000000000022" L2ToL1MessagePasser = "0x4200000000000000000000000000000000000016" DeployerWhitelist = "0x4200000000000000000000000000000000000002" WETH9 = "0x4200000000000000000000000000000000000006" @@ -39,6 +40,7 @@ const ( ) var ( + RevenueSharerAddr = common.HexToAddress(RevenueSharer) L2ToL1MessagePasserAddr = common.HexToAddress(L2ToL1MessagePasser) DeployerWhitelistAddr = common.HexToAddress(DeployerWhitelist) WETH9Addr = common.HexToAddress(WETH9) @@ -75,6 +77,7 @@ var ( ) func init() { + Predeploys["RevenueSharer"] = &Predeploy{Address: RevenueSharerAddr} Predeploys["L2ToL1MessagePasser"] = &Predeploy{Address: L2ToL1MessagePasserAddr} Predeploys["DeployerWhitelist"] = &Predeploy{Address: DeployerWhitelistAddr} Predeploys["WETH9"] = &Predeploy{Address: WETH9Addr, ProxyDisabled: true} diff --git a/packages/contracts-bedrock/scripts/Artifacts.s.sol b/packages/contracts-bedrock/scripts/Artifacts.s.sol index 253989b25ebf..2a59e878d342 100644 --- a/packages/contracts-bedrock/scripts/Artifacts.s.sol +++ b/packages/contracts-bedrock/scripts/Artifacts.s.sol @@ -107,7 +107,9 @@ abstract contract Artifacts { } bytes32 digest = keccak256(bytes(_name)); - if (digest == keccak256(bytes("L2CrossDomainMessenger"))) { + if (digest == keccak256(bytes("RevenueSharer"))) { + return payable(Predeploys.REVENUE_SHARER); + } else if (digest == keccak256(bytes("L2CrossDomainMessenger"))) { return payable(Predeploys.L2_CROSS_DOMAIN_MESSENGER); } else if (digest == keccak256(bytes("L2ToL1MessagePasser"))) { return payable(Predeploys.L2_TO_L1_MESSAGE_PASSER); From 41b2cb9cd76a4d8dfb1cc56f9cbd206edfeec1b5 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 15 Apr 2024 11:07:27 +0100 Subject: [PATCH 04/16] rewrite revenuesharer contract --- .../src/L2/RevenueSharer.sol | 123 +++++++----------- .../src/libraries/Predeploys.sol | 4 +- 2 files changed, 52 insertions(+), 75 deletions(-) diff --git a/packages/contracts-bedrock/src/L2/RevenueSharer.sol b/packages/contracts-bedrock/src/L2/RevenueSharer.sol index 63606a4f4f35..1b587faf1a6d 100644 --- a/packages/contracts-bedrock/src/L2/RevenueSharer.sol +++ b/packages/contracts-bedrock/src/L2/RevenueSharer.sol @@ -1,6 +1,4 @@ // SPDX-License-Identifier: MIT -// TODO gk: this has been largely copy pasted from -// https://github.com/base-org/contracts/blob/main/src/revenue-share/FeeDisburser.sol pragma solidity 0.8.15; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; @@ -14,7 +12,7 @@ import { SafeCall } from "src/libraries/SafeCall.sol"; /// @custom:predeploy 0x4200000000000000000000000000000000000022 /// @title RevenueSharer /// @dev Withdraws funds from system FeeVault contracts, -/// pays a share of revenue to Optimism +/// pays a share of revenue to a designated Beneficiary /// and sends the remainder to a configurable adddress on L1. contract RevenueSharer { /*////////////////////////////////////////////////////////////// @@ -29,15 +27,15 @@ contract RevenueSharer { */ uint32 public constant WITHDRAWAL_MIN_GAS = 35_000; /** - * @dev The net revenue percentage denominated in basis points that is used in + * @dev The percentage coeffieicnt of revenue denominated in basis points that is used in * Optimism revenue share calculation. */ - uint256 public constant OPTIMISM_NET_REVENUE_SHARE_BASIS_POINTS = 1_500; + uint256 public constant REVENUE_COEFFICIENT_BASIS_POINTS = 1_500; /** - * @dev The gross revenue percentage denominated in basis points that is used in + * @dev The percentage coefficient of profit denominated in basis points that is used in * Optimism revenue share calculation. */ - uint256 public constant OPTIMISM_GROSS_REVENUE_SHARE_BASIS_POINTS = 250; + uint256 public constant PROFIT_COEFFICIENT_BASIS_POINTS = 250; /*////////////////////////////////////////////////////////////// Immutables @@ -45,7 +43,7 @@ contract RevenueSharer { /** * @dev The address of the Optimism wallet that will receive Optimism's revenue share. */ - address payable public immutable OPTIMISM_WALLET; + address payable public immutable BENEFICIARY; /** * @dev The address of the L1 wallet that will receive the OP chain runner's share of fees. */ @@ -75,10 +73,10 @@ contract RevenueSharer { /** * @dev Emitted when fees are disbursed. * @param _disbursementTime The time of the disbursement. - * @param _paidToOptimism The amount of fees disbursed to Optimism. - * @param _totalFeesDisbursed The total amount of fees disbursed. + * @param _share The amount of fees shared to the Beneficiary. + * @param _total The total funds distributed. */ - event FeesDisbursed(uint256 _disbursementTime, uint256 _paidToOptimism, uint256 _totalFeesDisbursed); + event FeesDisbursed(uint256 _disbursementTime, uint256 _share, uint256 _total); /** * @dev Emitted when fees are received from FeeVaults. * @param _sender The FeeVault that sent the fees. @@ -95,18 +93,18 @@ contract RevenueSharer { //////////////////////////////////////////////////////////////*/ /** * @dev Constructor for the FeeDisburser contract which validates and sets immutable variables. - * @param _optimismWallet The address which receives Optimism's revenue share. + * @param _beneficiary The address which receives the revenue share. * @param _l1Wallet The L1 address which receives the remainder of the revenue. * @param _feeDisbursementInterval The minimum amount of time in seconds that must pass between fee disbursals. */ - constructor(address payable _optimismWallet, address _l1Wallet, uint256 _feeDisbursementInterval) { - require(_optimismWallet != address(0), "FeeDisburser: OptimismWallet cannot be address(0)"); + constructor(address payable _beneficiary, address _l1Wallet, uint256 _feeDisbursementInterval) { + require(_beneficiary != address(0), "FeeDisburser: OptimismWallet cannot be address(0)"); require(_l1Wallet != address(0), "FeeDisburser: L1Wallet cannot be address(0)"); require( _feeDisbursementInterval >= 24 hours, "FeeDisburser: FeeDisbursementInterval cannot be less than 24 hours" ); - OPTIMISM_WALLET = _optimismWallet; + BENEFICIARY = _beneficiary; L1_WALLET = _l1Wallet; FEE_DISBURSEMENT_INTERVAL = _feeDisbursementInterval; } @@ -116,61 +114,40 @@ contract RevenueSharer { //////////////////////////////////////////////////////////////*/ /** * @dev Withdraws funds from FeeVaults, sends Optimism their revenue share, and withdraws remaining funds to L1. - * @dev Implements revenue share business logic as follows: - * Net Revenue = sequencer FeeVault fee revenue + base FeeVault fee revenue - * Gross Revenue = Net Revenue + l1 FeeVault fee revenue - * Optimism Revenue Share = Maximum of 15% of Net Revenue and 2.5% of Gross Revenue - * L1 Wallet Revenue Share = Gross Revenue - Optimism Revenue Share */ - function disburseFees() external virtual { - require( - block.timestamp >= lastDisbursementTime + FEE_DISBURSEMENT_INTERVAL, - "FeeDisburser: Disbursement interval not reached" - ); - - // Sequencer and base FeeVaults will withdraw fees to the FeeDisburser contract mutating netFeeRevenue - feeVaultWithdrawal(payable(Predeploys.SEQUENCER_FEE_WALLET)); - feeVaultWithdrawal(payable(Predeploys.BASE_FEE_VAULT)); - - feeVaultWithdrawal(payable(Predeploys.L1_FEE_VAULT)); - - // Gross revenue is the sum of all fees - uint256 feeBalance = address(this).balance; - - // TODO gk: etFeeRevenue = feeBalance - l1fees . Be clearer to just subtract this off, and better anticipates - // future improvements - // where we may be tracking the actual expenditure on L1 - - // Stop execution if no fees were collected - if (feeBalance == 0) { - emit NoFeesCollected(); - return; - } - - lastDisbursementTime = block.timestamp; + function execute() external virtual { + // Pull in revenue + uint256 d = feeVaultWithdrawal(Predeploys.L1_FEE_VAULT); + uint256 b = feeVaultWithdrawal(Predeploys.BASE_FEE_VAULT); + uint256 q = feeVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET); - // Net revenue is the sum of sequencer fees and base fees - uint256 optimismNetRevenueShare = netFeeRevenue * OPTIMISM_NET_REVENUE_SHARE_BASIS_POINTS / BASIS_POINT_SCALE; - netFeeRevenue = 0; + // Compute expenditure + uint256 e = getL1FeeExpenditure(); - uint256 optimismGrossRevenueShare = feeBalance * OPTIMISM_GROSS_REVENUE_SHARE_BASIS_POINTS / BASIS_POINT_SCALE; + // Compute revenue and profit + uint256 r = d + b + q; // revenue + uint256 p = r - e; // profit - // Optimism's revenue share is the maximum of net and gross revenue - // TODO gk I think the wording can be improved here // Optimism's revenue share is the maximum of the - // respective shares of the net and gross revenue - uint256 optimismRevenueShare = Math.max(optimismNetRevenueShare, optimismGrossRevenueShare); + // Compute revenue share + uint256 s = Math.max( + REVENUE_COEFFICIENT_BASIS_POINTS * r / BASIS_POINT_SCALE, + PROFIT_COEFFICIENT_BASIS_POINTS * p / BASIS_POINT_SCALE + ); // share + uint256 remainder = r - s; - // Send Optimism their revenue share on L2 - require( - SafeCall.send(OPTIMISM_WALLET, gasleft(), optimismRevenueShare), - "FeeDisburser: Failed to send funds to Optimism" - ); + // Send Beneficiary their revenue share on L2 + require(SafeCall.send(BENEFICIARY, gasleft(), s), "RevenueSharer: Failed to send funds to Beneficiary"); // Send remaining funds to L1 wallet on L1 - L2StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)).bridgeETHTo{ value: address(this).balance }( + L2StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)).bridgeETHTo{ value: remainder }( L1_WALLET, WITHDRAWAL_MIN_GAS, bytes("") ); - emit FeesDisbursed(lastDisbursementTime, optimismRevenueShare, feeBalance); + + emit FeesDisbursed(lastDisbursementTime, s, r); + } + + function getL1FeeExpenditure() public pure returns (uint256) { + return 0; } /** @@ -178,13 +155,11 @@ contract RevenueSharer { * @dev Will revert if ETH is not sent from L2 FeeVaults. */ receive() external payable virtual { - if (msg.sender == Predeploys.SEQUENCER_FEE_WALLET || msg.sender == Predeploys.BASE_FEE_VAULT) { - // Adds value received to net fee revenue if the sender is the sequencer or base FeeVault - netFeeRevenue += msg.value; // TODO GK: be better to check balance before and after each FeeVault.withdraw() - // and explicitly label each chunk of ETH pulled in. The tracking of the fees from each vault is hard to - // reason about as is. - } else if (msg.sender != Predeploys.L1_FEE_VAULT) { - revert("FeeDisburser: Only FeeVaults can send ETH to FeeDisburser"); + if ( + msg.sender != Predeploys.SEQUENCER_FEE_WALLET && msg.sender != Predeploys.BASE_FEE_VAULT + && msg.sender != Predeploys.L1_FEE_VAULT + ) { + revert("RevenueSharer: Only FeeVaults can send ETH to FeeDisburser"); } emit FeesReceived(msg.sender, msg.value); } @@ -193,22 +168,24 @@ contract RevenueSharer { Internal Functions //////////////////////////////////////////////////////////////*/ /** - * @dev Withdraws fees from a FeeVault. + * @dev Withdraws fees from a FeeVault and returns the amount withdrawn. * @param _feeVault The address of the FeeVault to withdraw from. * @dev Withdrawal will only occur if the given FeeVault's balance is greater than or equal to * the minimum withdrawal amount. */ - function feeVaultWithdrawal(address payable _feeVault) internal { + function feeVaultWithdrawal(address payable _feeVault) internal returns (uint256) { require( FeeVault(_feeVault).WITHDRAWAL_NETWORK() == FeeVault.WithdrawalNetwork.L2, - "FeeDisburser: FeeVault must withdraw to L2" + "RevenueSharer: FeeVault must withdraw to L2" ); require( FeeVault(_feeVault).RECIPIENT() == address(this), - "FeeDisburser: FeeVault must withdraw to FeeDisburser contract" + "RevenueSharer: FeeVault must withdraw to RevenueSharer contract" ); + uint256 initial_balance = address(this).balance; if (_feeVault.balance >= FeeVault(_feeVault).MIN_WITHDRAWAL_AMOUNT()) { - FeeVault(_feeVault).withdraw(); + FeeVault(_feeVault).withdraw(); // TODO do we need a reentrancy guard around this? } + return address(this).balance - initial_balance; } } diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index 74a7362f6ad9..38b80b9e0e77 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -66,10 +66,10 @@ library Predeploys { address internal constant PROXY_ADMIN = 0x4200000000000000000000000000000000000018; /// @notice Address of the BaseFeeVault predeploy. - address internal constant BASE_FEE_VAULT = 0x4200000000000000000000000000000000000019; + address payable internal constant BASE_FEE_VAULT = payable(0x4200000000000000000000000000000000000019); /// @notice Address of the L1FeeVault predeploy. - address internal constant L1_FEE_VAULT = 0x420000000000000000000000000000000000001A; + address payable internal constant L1_FEE_VAULT = payable(0x420000000000000000000000000000000000001A); /// @notice Address of the SchemaRegistry predeploy. address internal constant SCHEMA_REGISTRY = 0x4200000000000000000000000000000000000020; From 98dbfdb7e71ae37b20f790d0c188fefd527709e6 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 15 Apr 2024 11:09:37 +0100 Subject: [PATCH 05/16] add natspec for temporary getL1FeeExpenditure getter --- packages/contracts-bedrock/src/L2/RevenueSharer.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/contracts-bedrock/src/L2/RevenueSharer.sol b/packages/contracts-bedrock/src/L2/RevenueSharer.sol index 1b587faf1a6d..64dbe1f00ca7 100644 --- a/packages/contracts-bedrock/src/L2/RevenueSharer.sol +++ b/packages/contracts-bedrock/src/L2/RevenueSharer.sol @@ -146,6 +146,10 @@ contract RevenueSharer { emit FeesDisbursed(lastDisbursementTime, s, r); } + /** + * @dev Returns the RevenueSharer's best estimate of L1 Fee expenditure for the current accounting period. + * @dev TODO this just returns zero for now, until L1 Fee Expenditure can be tracked on L2. + */ function getL1FeeExpenditure() public pure returns (uint256) { return 0; } From 9e04573aed2dad184a7bbeaba518723e63075017 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 15 Apr 2024 11:11:53 +0100 Subject: [PATCH 06/16] strip down RevenueSharer contract --- .../src/L2/RevenueSharer.sol | 38 ++----------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/packages/contracts-bedrock/src/L2/RevenueSharer.sol b/packages/contracts-bedrock/src/L2/RevenueSharer.sol index 64dbe1f00ca7..7daae7a941ec 100644 --- a/packages/contracts-bedrock/src/L2/RevenueSharer.sol +++ b/packages/contracts-bedrock/src/L2/RevenueSharer.sol @@ -48,45 +48,22 @@ contract RevenueSharer { * @dev The address of the L1 wallet that will receive the OP chain runner's share of fees. */ address public immutable L1_WALLET; - /** - * @dev The minimum amount of time in seconds that must pass between fee disbursals. - */ - uint256 public immutable FEE_DISBURSEMENT_INTERVAL; - - /*////////////////////////////////////////////////////////////// - Variables - //////////////////////////////////////////////////////////////*/ - /** - * @dev The timestamp of the last disbursal. - */ - uint256 public lastDisbursementTime; - /** - * @dev Tracks aggregate net fee revenue which is the sum of sequencer and base fees. - * @dev Explicity tracking Net Revenue is required to seperate L1FeeVault initiated - * withdrawals from Net Revenue calculations. - */ - uint256 public netFeeRevenue; /*////////////////////////////////////////////////////////////// Events //////////////////////////////////////////////////////////////*/ /** * @dev Emitted when fees are disbursed. - * @param _disbursementTime The time of the disbursement. * @param _share The amount of fees shared to the Beneficiary. * @param _total The total funds distributed. */ - event FeesDisbursed(uint256 _disbursementTime, uint256 _share, uint256 _total); + event FeesDisbursed(uint256 _share, uint256 _total); /** * @dev Emitted when fees are received from FeeVaults. * @param _sender The FeeVault that sent the fees. * @param _amount The amount of fees received. */ event FeesReceived(address indexed _sender, uint256 _amount); - /** - * @dev Emitted when no fees are collected from FeeVaults at time of disbursement. - */ - event NoFeesCollected(); /*////////////////////////////////////////////////////////////// Constructor @@ -95,18 +72,13 @@ contract RevenueSharer { * @dev Constructor for the FeeDisburser contract which validates and sets immutable variables. * @param _beneficiary The address which receives the revenue share. * @param _l1Wallet The L1 address which receives the remainder of the revenue. - * @param _feeDisbursementInterval The minimum amount of time in seconds that must pass between fee disbursals. */ - constructor(address payable _beneficiary, address _l1Wallet, uint256 _feeDisbursementInterval) { + constructor(address payable _beneficiary, address _l1Wallet) { require(_beneficiary != address(0), "FeeDisburser: OptimismWallet cannot be address(0)"); require(_l1Wallet != address(0), "FeeDisburser: L1Wallet cannot be address(0)"); - require( - _feeDisbursementInterval >= 24 hours, "FeeDisburser: FeeDisbursementInterval cannot be less than 24 hours" - ); BENEFICIARY = _beneficiary; L1_WALLET = _l1Wallet; - FEE_DISBURSEMENT_INTERVAL = _feeDisbursementInterval; } /*////////////////////////////////////////////////////////////// @@ -143,7 +115,7 @@ contract RevenueSharer { L1_WALLET, WITHDRAWAL_MIN_GAS, bytes("") ); - emit FeesDisbursed(lastDisbursementTime, s, r); + emit FeesDisbursed(s, r); } /** @@ -187,9 +159,7 @@ contract RevenueSharer { "RevenueSharer: FeeVault must withdraw to RevenueSharer contract" ); uint256 initial_balance = address(this).balance; - if (_feeVault.balance >= FeeVault(_feeVault).MIN_WITHDRAWAL_AMOUNT()) { - FeeVault(_feeVault).withdraw(); // TODO do we need a reentrancy guard around this? - } + FeeVault(_feeVault).withdraw(); // TODO do we need a reentrancy guard around this? return address(this).balance - initial_balance; } } From 573b81fc1fc80098eda713edf8678458a1b33069 Mon Sep 17 00:00:00 2001 From: geoknee Date: Thu, 18 Apr 2024 14:35:22 +0100 Subject: [PATCH 07/16] add RevenueSharer.t.sol and related changes to deployconfig etc failing on "proxy not initialized" --- .../deploy-config/hardhat.json | 4 +- .../scripts/DeployConfig.s.sol | 4 ++ .../src/libraries/Predeploys.sol | 2 +- .../test/L2/RevenueSharer.t.sol | 68 +++++++++++++++++++ .../contracts-bedrock/test/setup/Setup.sol | 3 + 5 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 packages/contracts-bedrock/test/L2/RevenueSharer.t.sol diff --git a/packages/contracts-bedrock/deploy-config/hardhat.json b/packages/contracts-bedrock/deploy-config/hardhat.json index cc5571117e58..6068c1d42c89 100644 --- a/packages/contracts-bedrock/deploy-config/hardhat.json +++ b/packages/contracts-bedrock/deploy-config/hardhat.json @@ -27,6 +27,8 @@ "baseFeeVaultRecipient": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "l1FeeVaultRecipient": "0x71bE63f3384f5fb98995898A86B02Fb2426c5788", "sequencerFeeVaultRecipient": "0xfabb0ac9d68b0b445fb7357272ff202c5651694a", + "revenueShareRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa", + "revenueShareRemainderRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa", "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", @@ -65,4 +67,4 @@ "daResolveWindow": 100, "daBondSize": 1000, "daResolverRefundPercentage": 50 -} +} \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/DeployConfig.s.sol index 4321373c101b..b642e1d0e71f 100644 --- a/packages/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/DeployConfig.s.sol @@ -42,6 +42,8 @@ contract DeployConfig is Script { address public sequencerFeeVaultRecipient; uint256 public sequencerFeeVaultMinimumWithdrawalAmount; uint256 public sequencerFeeVaultWithdrawalNetwork; + address public revenueShareRecipient; + address public revenueShareRemainderRecipient; string public governanceTokenName; string public governanceTokenSymbol; address public governanceTokenOwner; @@ -110,6 +112,8 @@ contract DeployConfig is Script { sequencerFeeVaultRecipient = stdJson.readAddress(_json, "$.sequencerFeeVaultRecipient"); sequencerFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.sequencerFeeVaultMinimumWithdrawalAmount"); sequencerFeeVaultWithdrawalNetwork = stdJson.readUint(_json, "$.sequencerFeeVaultWithdrawalNetwork"); + revenueShareRecipient = stdJson.readAddress(_json, "$.revenueShareRecipient"); + revenueShareRemainderRecipient = stdJson.readAddress(_json, "$.revenueShareRemainderRecipient"); governanceTokenName = stdJson.readString(_json, "$.governanceTokenName"); governanceTokenSymbol = stdJson.readString(_json, "$.governanceTokenSymbol"); governanceTokenOwner = stdJson.readAddress(_json, "$.governanceTokenOwner"); diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index 38b80b9e0e77..404c5828925f 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -9,7 +9,7 @@ library Predeploys { uint256 internal constant PREDEPLOY_COUNT = 2048; /// @notice Address of the RevenueSharer predeploy. - address internal constant REVENUE_SHARER = 0x4200000000000000000000000000000000000022; + address payable internal constant REVENUE_SHARER = payable(0x4200000000000000000000000000000000000024); /// @custom:legacy /// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated diff --git a/packages/contracts-bedrock/test/L2/RevenueSharer.t.sol b/packages/contracts-bedrock/test/L2/RevenueSharer.t.sol new file mode 100644 index 000000000000..9a622ac6c485 --- /dev/null +++ b/packages/contracts-bedrock/test/L2/RevenueSharer.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing utilities +import { CommonTest } from "test/setup/CommonTest.sol"; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; + +// Target contract dependencies +import { FeeVault } from "src/universal/FeeVault.sol"; +import { SequencerFeeVault } from "src/L2/SequencerFeeVault.sol"; +import { BaseFeeVault } from "src/L2/BaseFeeVault.sol"; +import { L1FeeVault } from "src/L2/L1FeeVault.sol"; + +// Target contract +import { RevenueSharer } from "src/L2/RevenueSharer.sol"; + +contract RevenueSharer_Test is CommonTest { + address recipient; + address remainderRecipient; + + /// @dev Sets up the test suite. + function setUp() public override { + super.setUp(); + recipient = deploy.cfg().revenueShareRecipient(); + remainderRecipient = deploy.cfg().revenueShareRemainderRecipient(); + } + + /// @dev Tests that the l1 fee wallet is correct. + function test_constructor_succeeds() external { + // TODO + } + + /// @dev Tests that the fee vault is able to receive ETH. + function test_execute_succeeds() external { + // Deal some ETH to the fee vaults, sanity check the results + vm.deal(address(sequencerFeeVault), 120); + assertEq(address(sequencerFeeVault).balance, 120); + + vm.deal(address(l1FeeVault), 70); + assertEq(address(l1FeeVault).balance, 70); + + vm.deal(address(baseFeeVault), 110); + assertEq(address(baseFeeVault).balance, 110); + + // Setup assertion that an event will be emitted + // vm.expectEmit(Predeploys.SEQUENCER_FEE_WALLET); + // emit FeesDisbursed(45, 300); + + // Execute + revenueSharer.execute(); + + // Assert 15% of revenue flows to beneficiary + assertEq(recipient.balance, 45); + + // Assert 85% of revenue flows to other party + assertEq(remainderRecipient.balance, 255); + + // Assert RevenueSharer does not accumulate ETH + assertEq(address(revenueSharer).balance, 0); + + // Assert FeeVaults are depleted + assertEq(address(sequencerFeeVault).balance, 0); + assertEq(address(l1FeeVault).balance, 0); + assertEq(address(baseFeeVault).balance, 0); + } +} diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index d585e5aff4a6..1276f0446e64 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -11,6 +11,7 @@ import { L2ERC721Bridge } from "src/L2/L2ERC721Bridge.sol"; import { BaseFeeVault } from "src/L2/BaseFeeVault.sol"; import { SequencerFeeVault } from "src/L2/SequencerFeeVault.sol"; import { L1FeeVault } from "src/L2/L1FeeVault.sol"; +import { RevenueSharer } from "src/L2/RevenueSharer.sol"; import { GasPriceOracle } from "src/L2/GasPriceOracle.sol"; import { L1Block } from "src/L2/L1Block.sol"; import { LegacyMessagePasser } from "src/legacy/LegacyMessagePasser.sol"; @@ -86,6 +87,7 @@ contract Setup { BaseFeeVault baseFeeVault = BaseFeeVault(payable(Predeploys.BASE_FEE_VAULT)); SequencerFeeVault sequencerFeeVault = SequencerFeeVault(payable(Predeploys.SEQUENCER_FEE_WALLET)); L1FeeVault l1FeeVault = L1FeeVault(payable(Predeploys.L1_FEE_VAULT)); + RevenueSharer revenueSharer = RevenueSharer(Predeploys.REVENUE_SHARER); GasPriceOracle gasPriceOracle = GasPriceOracle(Predeploys.GAS_PRICE_ORACLE); L1Block l1Block = L1Block(Predeploys.L1_BLOCK_ATTRIBUTES); LegacyMessagePasser legacyMessagePasser = LegacyMessagePasser(Predeploys.LEGACY_MESSAGE_PASSER); @@ -204,6 +206,7 @@ contract Setup { labelPredeploy(Predeploys.GOVERNANCE_TOKEN); labelPredeploy(Predeploys.EAS); labelPredeploy(Predeploys.SCHEMA_REGISTRY); + labelPredeploy(Predeploys.REVENUE_SHARER); // L2 Preinstalls labelPreinstall(Preinstalls.MultiCall3); From 638cd3b99232150d3e64450be412826010c43b6e Mon Sep 17 00:00:00 2001 From: geoknee Date: Thu, 18 Apr 2024 17:17:10 +0100 Subject: [PATCH 08/16] make revenuesharer constructor params payable --- packages/contracts-bedrock/scripts/DeployConfig.s.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/DeployConfig.s.sol index b642e1d0e71f..fdca7c2252f9 100644 --- a/packages/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/DeployConfig.s.sol @@ -42,8 +42,8 @@ contract DeployConfig is Script { address public sequencerFeeVaultRecipient; uint256 public sequencerFeeVaultMinimumWithdrawalAmount; uint256 public sequencerFeeVaultWithdrawalNetwork; - address public revenueShareRecipient; - address public revenueShareRemainderRecipient; + address payable public revenueShareRecipient; + address payable public revenueShareRemainderRecipient; string public governanceTokenName; string public governanceTokenSymbol; address public governanceTokenOwner; @@ -112,8 +112,8 @@ contract DeployConfig is Script { sequencerFeeVaultRecipient = stdJson.readAddress(_json, "$.sequencerFeeVaultRecipient"); sequencerFeeVaultMinimumWithdrawalAmount = stdJson.readUint(_json, "$.sequencerFeeVaultMinimumWithdrawalAmount"); sequencerFeeVaultWithdrawalNetwork = stdJson.readUint(_json, "$.sequencerFeeVaultWithdrawalNetwork"); - revenueShareRecipient = stdJson.readAddress(_json, "$.revenueShareRecipient"); - revenueShareRemainderRecipient = stdJson.readAddress(_json, "$.revenueShareRemainderRecipient"); + revenueShareRecipient = payable(stdJson.readAddress(_json, "$.revenueShareRecipient")); + revenueShareRemainderRecipient = payable(stdJson.readAddress(_json, "$.revenueShareRemainderRecipient")); governanceTokenName = stdJson.readString(_json, "$.governanceTokenName"); governanceTokenSymbol = stdJson.readString(_json, "$.governanceTokenSymbol"); governanceTokenOwner = stdJson.readAddress(_json, "$.governanceTokenOwner"); From 361a02394cbea35f26fd8adf0b8455c15ad92934 Mon Sep 17 00:00:00 2001 From: geoknee Date: Fri, 19 Apr 2024 17:13:40 +0100 Subject: [PATCH 09/16] fix incorrect predeploy addresses --- packages/contracts-bedrock/src/L2/RevenueSharer.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/src/L2/RevenueSharer.sol b/packages/contracts-bedrock/src/L2/RevenueSharer.sol index 7daae7a941ec..c374348f8f25 100644 --- a/packages/contracts-bedrock/src/L2/RevenueSharer.sol +++ b/packages/contracts-bedrock/src/L2/RevenueSharer.sol @@ -9,7 +9,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; /// @custom:proxied -/// @custom:predeploy 0x4200000000000000000000000000000000000022 +/// @custom:predeploy 0x4200000000000000000000000000000000000024 /// @title RevenueSharer /// @dev Withdraws funds from system FeeVault contracts, /// pays a share of revenue to a designated Beneficiary From 52b65cf7047d21f6d9564df5665873b94663d464 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 29 Apr 2024 11:58:45 +0100 Subject: [PATCH 10/16] make SEQUENCER_FEE_WALLET payable in Predeploys.sol --- packages/contracts-bedrock/src/libraries/Predeploys.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index 404c5828925f..d9b22b778b02 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -40,7 +40,7 @@ library Predeploys { address internal constant L2_STANDARD_BRIDGE = 0x4200000000000000000000000000000000000010; //// @notice Address of the SequencerFeeWallet predeploy. - address internal constant SEQUENCER_FEE_WALLET = 0x4200000000000000000000000000000000000011; + address payable internal constant SEQUENCER_FEE_WALLET = payable(0x4200000000000000000000000000000000000011); /// @notice Address of the OptimismMintableERC20Factory predeploy. address internal constant OPTIMISM_MINTABLE_ERC20_FACTORY = 0x4200000000000000000000000000000000000012; From c1eeaac58deddb648ae8cd02d318319269539551 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 29 Apr 2024 12:16:05 +0100 Subject: [PATCH 11/16] revshare: use custom errors throughout --- .../src/L2/RevenueSharer.sol | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/contracts-bedrock/src/L2/RevenueSharer.sol b/packages/contracts-bedrock/src/L2/RevenueSharer.sol index c374348f8f25..33e5aedb5502 100644 --- a/packages/contracts-bedrock/src/L2/RevenueSharer.sol +++ b/packages/contracts-bedrock/src/L2/RevenueSharer.sol @@ -8,6 +8,12 @@ import { FeeVault } from "src/universal/FeeVault.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; +error OnlyFeeVaults(); +error ZeroAddress(string); +error UnexpectedFeeVaultWithdrawalNetwork(); +error UnexpectedFeeVaultRecipient(); +error FailedToShare(); + /// @custom:proxied /// @custom:predeploy 0x4200000000000000000000000000000000000024 /// @title RevenueSharer @@ -32,7 +38,7 @@ contract RevenueSharer { */ uint256 public constant REVENUE_COEFFICIENT_BASIS_POINTS = 1_500; /** - * @dev The percentage coefficient of profit denominated in basis points that is used in + * @dev The percentage coefficient of profit denominated in bass points that is used in * Optimism revenue share calculation. */ uint256 public constant PROFIT_COEFFICIENT_BASIS_POINTS = 250; @@ -74,8 +80,8 @@ contract RevenueSharer { * @param _l1Wallet The L1 address which receives the remainder of the revenue. */ constructor(address payable _beneficiary, address _l1Wallet) { - require(_beneficiary != address(0), "FeeDisburser: OptimismWallet cannot be address(0)"); - require(_l1Wallet != address(0), "FeeDisburser: L1Wallet cannot be address(0)"); + if (_beneficiary == address(0)) revert ZeroAddress("_beneficiary"); + if (_l1Wallet == address(0)) revert ZeroAddress("_l1Wallet"); BENEFICIARY = _beneficiary; L1_WALLET = _l1Wallet; @@ -108,7 +114,9 @@ contract RevenueSharer { uint256 remainder = r - s; // Send Beneficiary their revenue share on L2 - require(SafeCall.send(BENEFICIARY, gasleft(), s), "RevenueSharer: Failed to send funds to Beneficiary"); + if (!SafeCall.send(BENEFICIARY, gasleft(), s)) { + revert FailedToShare(); + } // Send remaining funds to L1 wallet on L1 L2StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)).bridgeETHTo{ value: remainder }( @@ -135,7 +143,7 @@ contract RevenueSharer { msg.sender != Predeploys.SEQUENCER_FEE_WALLET && msg.sender != Predeploys.BASE_FEE_VAULT && msg.sender != Predeploys.L1_FEE_VAULT ) { - revert("RevenueSharer: Only FeeVaults can send ETH to FeeDisburser"); + revert OnlyFeeVaults(); } emit FeesReceived(msg.sender, msg.value); } @@ -150,14 +158,10 @@ contract RevenueSharer { * the minimum withdrawal amount. */ function feeVaultWithdrawal(address payable _feeVault) internal returns (uint256) { - require( - FeeVault(_feeVault).WITHDRAWAL_NETWORK() == FeeVault.WithdrawalNetwork.L2, - "RevenueSharer: FeeVault must withdraw to L2" - ); - require( - FeeVault(_feeVault).RECIPIENT() == address(this), - "RevenueSharer: FeeVault must withdraw to RevenueSharer contract" - ); + if (FeeVault(_feeVault).WITHDRAWAL_NETWORK() != FeeVault.WithdrawalNetwork.L2) { + revert UnexpectedFeeVaultWithdrawalNetwork(); + } + if (FeeVault(_feeVault).RECIPIENT() != address(this)) revert UnexpectedFeeVaultRecipient(); uint256 initial_balance = address(this).balance; FeeVault(_feeVault).withdraw(); // TODO do we need a reentrancy guard around this? return address(this).balance - initial_balance; From a06cdf22a06e1db8529f302a4f1eb87cfd45c850 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 29 Apr 2024 12:27:30 +0100 Subject: [PATCH 12/16] replace immutable variables with storage and use initializer --- .../contracts-bedrock/src/L2/RevenueSharer.sol | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/contracts-bedrock/src/L2/RevenueSharer.sol b/packages/contracts-bedrock/src/L2/RevenueSharer.sol index 33e5aedb5502..4424b58da6db 100644 --- a/packages/contracts-bedrock/src/L2/RevenueSharer.sol +++ b/packages/contracts-bedrock/src/L2/RevenueSharer.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.15; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { L2StandardBridge } from "src/L2/L2StandardBridge.sol"; import { FeeVault } from "src/universal/FeeVault.sol"; @@ -20,7 +21,7 @@ error FailedToShare(); /// @dev Withdraws funds from system FeeVault contracts, /// pays a share of revenue to a designated Beneficiary /// and sends the remainder to a configurable adddress on L1. -contract RevenueSharer { +contract RevenueSharer is Initializable { /*////////////////////////////////////////////////////////////// Constants //////////////////////////////////////////////////////////////*/ @@ -49,11 +50,11 @@ contract RevenueSharer { /** * @dev The address of the Optimism wallet that will receive Optimism's revenue share. */ - address payable public immutable BENEFICIARY; + address payable public BENEFICIARY; /** * @dev The address of the L1 wallet that will receive the OP chain runner's share of fees. */ - address public immutable L1_WALLET; + address public L1_WALLET; /*////////////////////////////////////////////////////////////// Events @@ -79,10 +80,13 @@ contract RevenueSharer { * @param _beneficiary The address which receives the revenue share. * @param _l1Wallet The L1 address which receives the remainder of the revenue. */ - constructor(address payable _beneficiary, address _l1Wallet) { + constructor(address payable _beneficiary, address payable _l1Wallet) { + initialize(_beneficiary, _l1Wallet); + } + + function initialize(address payable _beneficiary, address payable _l1Wallet) public initializer { if (_beneficiary == address(0)) revert ZeroAddress("_beneficiary"); if (_l1Wallet == address(0)) revert ZeroAddress("_l1Wallet"); - BENEFICIARY = _beneficiary; L1_WALLET = _l1Wallet; } From f2530b5c11d0efa281bba73f84ead42a06e41e5a Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 29 Apr 2024 12:31:24 +0100 Subject: [PATCH 13/16] replace TODO with comment about control flow --- packages/contracts-bedrock/src/L2/RevenueSharer.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/src/L2/RevenueSharer.sol b/packages/contracts-bedrock/src/L2/RevenueSharer.sol index 4424b58da6db..db63cc3769f3 100644 --- a/packages/contracts-bedrock/src/L2/RevenueSharer.sol +++ b/packages/contracts-bedrock/src/L2/RevenueSharer.sol @@ -167,7 +167,9 @@ contract RevenueSharer is Initializable { } if (FeeVault(_feeVault).RECIPIENT() != address(this)) revert UnexpectedFeeVaultRecipient(); uint256 initial_balance = address(this).balance; - FeeVault(_feeVault).withdraw(); // TODO do we need a reentrancy guard around this? + // The following line will call back into the receive() function on this contract, + // causing all of the ether from the fee vault to move to this contract: + FeeVault(_feeVault).withdraw(); return address(this).balance - initial_balance; } } From e477977084e274f51246266410a5a7ae154748ea Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 29 Apr 2024 13:25:27 +0100 Subject: [PATCH 14/16] fix address in predeploy bindings --- op-bindings/predeploys/addresses.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-bindings/predeploys/addresses.go b/op-bindings/predeploys/addresses.go index 11b909273135..7f58883f2179 100644 --- a/op-bindings/predeploys/addresses.go +++ b/op-bindings/predeploys/addresses.go @@ -6,7 +6,7 @@ import "github.com/ethereum/go-ethereum/common" // This needs to be kept in sync with @eth-optimism/contracts-ts/wagmi.config.ts which also specifies this // To improve robustness and maintainability contracts-bedrock should export all addresses const ( - RevenueSharer = "0x4200000000000000000000000000000000000022" + RevenueSharer = "0x4200000000000000000000000000000000000024" L2ToL1MessagePasser = "0x4200000000000000000000000000000000000016" DeployerWhitelist = "0x4200000000000000000000000000000000000002" WETH9 = "0x4200000000000000000000000000000000000006" From c941ce7e521b4c27a2107aacfc5ad54a7a6ab4e1 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 29 Apr 2024 14:21:27 +0100 Subject: [PATCH 15/16] change deploy config to allow revenue sharing --- .../contracts-bedrock/deploy-config/hardhat.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/contracts-bedrock/deploy-config/hardhat.json b/packages/contracts-bedrock/deploy-config/hardhat.json index 6068c1d42c89..cb57f85799b7 100644 --- a/packages/contracts-bedrock/deploy-config/hardhat.json +++ b/packages/contracts-bedrock/deploy-config/hardhat.json @@ -29,12 +29,12 @@ "sequencerFeeVaultRecipient": "0xfabb0ac9d68b0b445fb7357272ff202c5651694a", "revenueShareRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa", "revenueShareRemainderRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa", - "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", - "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", - "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", - "baseFeeVaultWithdrawalNetwork": 0, - "l1FeeVaultWithdrawalNetwork": 0, - "sequencerFeeVaultWithdrawalNetwork": 0, + "baseFeeVaultMinimumWithdrawalAmount": "0x0", + "l1FeeVaultMinimumWithdrawalAmount": "0x0", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x0", + "baseFeeVaultWithdrawalNetwork": 1, + "l1FeeVaultWithdrawalNetwork": 1, + "sequencerFeeVaultWithdrawalNetwork": 1, "enableGovernance": true, "governanceTokenName": "Optimism", "governanceTokenSymbol": "OP", From 82aea020d201e5acd2efb84054bdeaa735c0fd33 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 29 Apr 2024 14:21:49 +0100 Subject: [PATCH 16/16] add RevenueSharer to L2Genesis --- packages/contracts-bedrock/scripts/L2Genesis.s.sol | 9 +++++++++ packages/contracts-bedrock/src/libraries/Predeploys.sol | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 387520f907e8..03104e11dd26 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -16,6 +16,7 @@ import { GasPriceOracle } from "src/L2/GasPriceOracle.sol"; import { L2StandardBridge } from "src/L2/L2StandardBridge.sol"; import { L2ERC721Bridge } from "src/L2/L2ERC721Bridge.sol"; import { SequencerFeeVault } from "src/L2/SequencerFeeVault.sol"; +import { RevenueSharer } from "src/L2/RevenueSharer.sol"; import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol"; import { OptimismMintableERC721Factory } from "src/universal/OptimismMintableERC721Factory.sol"; import { BaseFeeVault } from "src/L2/BaseFeeVault.sol"; @@ -214,9 +215,17 @@ contract L2Genesis is Deployer { // 1B,1C,1D,1E,1F: not used. setSchemaRegistry(); // 20 setEAS(); // 21 + setRevenueSharer(); // 24 setGovernanceToken(); // 42: OP (not behind a proxy) } + function setRevenueSharer() public { + RevenueSharer(Predeploys.REVENUE_SHARER).initialize({ + _beneficiary: cfg.revenueShareRecipient(), + _l1Wallet: cfg.revenueShareRemainderRecipient() + }); + } + function setProxyAdmin() public { // Note the ProxyAdmin implementation itself is behind a proxy that owns itself. address impl = _setImplementationCode(Predeploys.PROXY_ADMIN); diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index d9b22b778b02..daf9b5858876 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -114,6 +114,7 @@ library Predeploys { if (_addr == GOVERNANCE_TOKEN) return "GovernanceToken"; if (_addr == LEGACY_ERC20_ETH) return "LegacyERC20ETH"; if (_addr == CROSS_L2_INBOX) return "CrossL2Inbox"; + if (_addr == REVENUE_SHARER) return "RevenueSharer"; revert("Predeploys: unnamed predeploy"); } @@ -129,7 +130,8 @@ library Predeploys { || _addr == SEQUENCER_FEE_WALLET || _addr == OPTIMISM_MINTABLE_ERC20_FACTORY || _addr == L1_BLOCK_NUMBER || _addr == L2_ERC721_BRIDGE || _addr == L1_BLOCK_ATTRIBUTES || _addr == L2_TO_L1_MESSAGE_PASSER || _addr == OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == PROXY_ADMIN || _addr == BASE_FEE_VAULT - || _addr == L1_FEE_VAULT || _addr == SCHEMA_REGISTRY || _addr == EAS || _addr == GOVERNANCE_TOKEN; + || _addr == L1_FEE_VAULT || _addr == SCHEMA_REGISTRY || _addr == EAS || _addr == GOVERNANCE_TOKEN + || _addr == REVENUE_SHARER; } function isPredeployNamespace(address _addr) internal pure returns (bool) {