@@ -59,63 +59,6 @@ contract FeeSharingCollector is
5959 address public constant RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT =
6060 address (uint160 (uint256 (keccak256 ("RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT " ))));
6161
62- /* Events */
63-
64- /// @notice Deprecated event after the unification between wrbtc & rbtc
65- // event FeeWithdrawn(address indexed sender, address indexed token, uint256 amount);
66- event FeeWithdrawnInRBTC (address indexed sender , uint256 amount );
67-
68- /// @notice An event emitted when tokens transferred.
69- event TokensTransferred (address indexed sender , address indexed token , uint256 amount );
70-
71- /// @notice An event emitted when checkpoint added.
72- event CheckpointAdded (address indexed sender , address indexed token , uint256 amount );
73-
74- /// @notice An event emitted when user fee get withdrawn.
75- event UserFeeWithdrawn (
76- address indexed sender ,
77- address indexed receiver ,
78- address indexed token ,
79- uint256 amount
80- );
81-
82- /// @notice An event emitted when user fee get withdrawn.
83- event UserFeeProcessedNoWithdraw (
84- address indexed sender ,
85- address indexed token ,
86- uint256 prevProcessedCheckpoints ,
87- uint256 newProcessedCheckpoints
88- );
89-
90- /**
91- * @notice An event emitted when fee from AMM get withdrawn.
92- *
93- * @param sender sender who initiate the withdrawn amm fees.
94- * @param converter the converter address.
95- * @param amount total amount of fee (Already converted to WRBTC).
96- */
97- event FeeAMMWithdrawn (address indexed sender , address indexed converter , uint256 amount );
98-
99- /// @notice An event emitted when converter address has been registered to be whitelisted.
100- event WhitelistedConverter (address indexed sender , address converter );
101-
102- /// @notice An event emitted when converter address has been removed from whitelist.
103- event UnwhitelistedConverter (address indexed sender , address converter );
104-
105- event RBTCWithdrawn (address indexed sender , address indexed receiver , uint256 amount );
106-
107- event SetWrbtcToken (
108- address indexed sender ,
109- address indexed oldWrbtcToken ,
110- address indexed newWrbtcToken
111- );
112-
113- event SetLoanTokenWrbtc (
114- address indexed sender ,
115- address indexed oldLoanTokenWrbtc ,
116- address indexed newLoanTokenWrbtc
117- );
118-
11962 /* Modifier */
12063 modifier oneTimeExecution (bytes4 _funcSig ) {
12164 require (
@@ -207,7 +150,7 @@ contract FeeSharingCollector is
207150 "FeeSharingCollector::withdrawFees: wrbtc token amount exceeds 96 bits "
208151 );
209152
210- _addCheckpoint (RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT, amount96);
153+ _addCheckpointOrWithholdProtocolFee (RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT, amount96);
211154 }
212155
213156 // note deprecated event since we unify the wrbtc & rbtc
@@ -257,7 +200,10 @@ contract FeeSharingCollector is
257200 }
258201
259202 if (totalPoolTokenAmount > 0 ) {
260- _addCheckpoint (RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT, totalPoolTokenAmount);
203+ _addCheckpointOrWithholdProtocolFee (
204+ RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT,
205+ totalPoolTokenAmount
206+ );
261207 }
262208 }
263209
@@ -283,7 +229,7 @@ contract FeeSharingCollector is
283229 _token = RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT;
284230 }
285231
286- _addCheckpoint (_token, _amount);
232+ _addCheckpointOrWithholdProtocolFee (_token, _amount);
287233
288234 emit TokensTransferred (msg .sender , _token, _amount);
289235 }
@@ -297,35 +243,45 @@ contract FeeSharingCollector is
297243 uint96 _amount = uint96 (msg .value );
298244 require (_amount > 0 , "FeeSharingCollector::transferRBTC: invalid value " );
299245
300- _addCheckpoint (RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT, _amount);
246+ _addCheckpointOrWithholdProtocolFee (RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT, _amount);
301247
302248 emit TokensTransferred (msg .sender , ZERO_ADDRESS, _amount);
303249 }
304250
305251 /**
306252 * @notice Add checkpoint with accumulated amount by function invocation.
253+ * @dev If token is in protocol withhold list, accumulates fees for protocol (no checkpoint for stakers).
254+ * @dev If token is not in withhold list, creates checkpoint for stakers (100% distribution).
255+ * @dev Does not create zero-value checkpoints to save gas.
307256 * @param _token Address of the token.
257+ * @param _amount Amount of tokens to process.
308258 * */
309- function _addCheckpoint (address _token , uint96 _amount ) internal {
310- if (block .timestamp - lastFeeWithdrawalTime[_token] >= FEE_WITHDRAWAL_INTERVAL) {
311- lastFeeWithdrawalTime[_token] = block .timestamp ;
312- uint96 amount = add96 (
313- unprocessedAmount[_token],
314- _amount,
315- "FeeSharingCollector::_addCheckpoint: amount exceeds 96 bits "
316- );
317-
318- /// @notice Reset unprocessed amount of tokens to zero.
319- unprocessedAmount[_token] = 0 ;
320-
321- /// @notice Write a regular checkpoint.
322- _writeTokenCheckpoint (_token, amount);
259+ function _addCheckpointOrWithholdProtocolFee (address _token , uint96 _amount ) internal {
260+ /// @notice Check if token is in protocol withhold list
261+ if (protocolWithheldTokensList.contains (_token)) {
262+ /// @notice Token is withheld by protocol - directly add to withheld fees (no unprocessedAmount accumulation)
263+ protocolWithheldFees[_token] = protocolWithheldFees[_token].add (_amount);
264+ emit ProtocolRevenueAccumulated (_token, _amount);
323265 } else {
324- unprocessedAmount[_token] = add96 (
325- unprocessedAmount[_token],
326- _amount,
327- "FeeSharingCollector::_addCheckpoint: unprocessedAmount exceeds 96 bits "
328- );
266+ /// @notice Token is distributed to stakers - use unprocessedAmount and interval logic
267+ if (block .timestamp - lastFeeWithdrawalTime[_token] >= FEE_WITHDRAWAL_INTERVAL) {
268+ lastFeeWithdrawalTime[_token] = block .timestamp ;
269+ uint96 amount = add96 (
270+ unprocessedAmount[_token],
271+ _amount,
272+ "FeeSharingCollector::_addCheckpointOrWithholdProtocolFee: amount exceeds 96 bits "
273+ );
274+ /// @notice Reset unprocessed amount of tokens to zero.
275+ unprocessedAmount[_token] = 0 ;
276+ /// @notice Create checkpoint for stakers
277+ _writeTokenCheckpoint (_token, amount);
278+ } else {
279+ unprocessedAmount[_token] = add96 (
280+ unprocessedAmount[_token],
281+ _amount,
282+ "FeeSharingCollector::_addCheckpointOrWithholdProtocolFee: unprocessedAmount exceeds 96 bits "
283+ );
284+ }
329285 }
330286 }
331287
@@ -1095,6 +1051,169 @@ contract FeeSharingCollector is
10951051 wrbtcToken.safeTransfer (receiver, wrbtcAmount);
10961052 }
10971053
1054+ /**
1055+ * @notice Add a token to the protocol withhold list.
1056+ * @dev Only callable by owner.
1057+ * @dev Fees from tokens in this list are withheld 100% by protocol (no staker distribution).
1058+ * @dev If there are unprocessed fees accumulated before adding to list, they are moved to protocolWithheldFees.
1059+ * @param token The token address to add.
1060+ */
1061+ function addProtocolWithholdToken (address token ) external onlyOwner {
1062+ require (
1063+ token != ZERO_ADDRESS,
1064+ "FeeSharingCollector::addProtocolWithholdToken: invalid token "
1065+ );
1066+ require (
1067+ ! protocolWithheldTokensList.contains (token),
1068+ "FeeSharingCollector::addProtocolWithholdToken: token already in list "
1069+ );
1070+
1071+ /// @notice Move any existing unprocessed amount to protocol withheld fees
1072+ if (unprocessedAmount[token] > 0 ) {
1073+ protocolWithheldFees[token] = protocolWithheldFees[token].add (
1074+ unprocessedAmount[token]
1075+ );
1076+ emit ProtocolRevenueAccumulated (token, unprocessedAmount[token]);
1077+ unprocessedAmount[token] = 0 ;
1078+ }
1079+
1080+ protocolWithheldTokensList.add (token);
1081+ emit TokenAddedToProtocolWithholdList (msg .sender , token);
1082+ }
1083+
1084+ /**
1085+ * @notice Remove a token from the protocol withhold list.
1086+ * @dev Only callable by owner.
1087+ * @dev Fees from tokens removed from this list will be distributed to stakers.
1088+ * @param token The token address to remove.
1089+ */
1090+ function removeProtocolWithholdToken (address token ) external onlyOwner {
1091+ require (
1092+ protocolWithheldTokensList.contains (token),
1093+ "FeeSharingCollector::removeProtocolWithholdToken: token not in list "
1094+ );
1095+
1096+ protocolWithheldTokensList.remove (token);
1097+ emit TokenRemovedFromProtocolWithholdList (msg .sender , token);
1098+ }
1099+
1100+ /**
1101+ * @notice Check if a token is in the protocol withhold list.
1102+ * @param token The token address to check.
1103+ * @return True if token fees are withheld by protocol, false otherwise.
1104+ */
1105+ function isTokenInProtocolWithholdList (address token ) external view returns (bool ) {
1106+ return protocolWithheldTokensList.contains (token);
1107+ }
1108+
1109+ /**
1110+ * @notice Get all tokens in the protocol withhold list.
1111+ * @return Array of token addresses that are withheld by protocol.
1112+ */
1113+ function getProtocolWithholdTokensList () external view returns (address [] memory ) {
1114+ uint256 length = protocolWithheldTokensList.length ();
1115+ address [] memory tokens = new address [](length);
1116+ for (uint256 i = 0 ; i < length; i++ ) {
1117+ tokens[i] = protocolWithheldTokensList.get (i);
1118+ }
1119+ return tokens;
1120+ }
1121+
1122+ /**
1123+ * @notice Withdraw all accumulated protocol withheld fees for a token.
1124+ * @dev Only callable by owner. Withdraws the full balance.
1125+ * @param _token The token address to withdraw.
1126+ * Use RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT for RBTC.
1127+ * @param _receiver The address to receive the withheld fees.
1128+ */
1129+ function withdrawProtocolWithheldFees (
1130+ address _token ,
1131+ address _receiver
1132+ ) external onlyOwner nonReentrant {
1133+ require (
1134+ _receiver != ZERO_ADDRESS,
1135+ "FeeSharingCollector::withdrawProtocolWithheldFees: invalid receiver "
1136+ );
1137+
1138+ uint256 amount = protocolWithheldFees[_token];
1139+ require (
1140+ amount > 0 ,
1141+ "FeeSharingCollector::withdrawProtocolWithheldFees: no fees to withdraw "
1142+ );
1143+
1144+ protocolWithheldFees[_token] = 0 ;
1145+
1146+ // Handle RBTC vs ERC20 token withdrawal
1147+ if (_token == RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT) {
1148+ // Transfer RBTC
1149+ (bool success , ) = _receiver.call.value (amount)("" );
1150+ require (
1151+ success,
1152+ "FeeSharingCollector::withdrawProtocolWithheldFees: RBTC transfer failed "
1153+ );
1154+ } else {
1155+ // Transfer ERC20 token
1156+ IERC20 (_token).safeTransfer (_receiver, amount);
1157+ }
1158+
1159+ emit ProtocolWithheldFeesWithdrawn (msg .sender , _receiver, _token, amount);
1160+ }
1161+
1162+ /**
1163+ * @notice Withdraw all accumulated protocol withheld fees for multiple tokens in batch.
1164+ * @dev Only callable by owner. Withdraws full balance for each token to the same receiver.
1165+ * @param _tokens Array of token addresses to withdraw.
1166+ * @param _receiver The address to receive all withheld fees.
1167+ */
1168+ function withdrawProtocolWithheldFeesBatch (
1169+ address [] calldata _tokens ,
1170+ address _receiver
1171+ ) external onlyOwner nonReentrant {
1172+ require (
1173+ _receiver != ZERO_ADDRESS,
1174+ "FeeSharingCollector::withdrawProtocolWithheldFeesBatch: invalid receiver "
1175+ );
1176+ require (
1177+ _tokens.length > 0 ,
1178+ "FeeSharingCollector::withdrawProtocolWithheldFeesBatch: empty array "
1179+ );
1180+
1181+ for (uint256 i = 0 ; i < _tokens.length ; i++ ) {
1182+ address token = _tokens[i];
1183+ uint256 amount = protocolWithheldFees[token];
1184+
1185+ if (amount == 0 ) {
1186+ continue ; // Skip tokens with no fees
1187+ }
1188+
1189+ protocolWithheldFees[token] = 0 ;
1190+
1191+ // Handle RBTC vs ERC20 token withdrawal
1192+ if (token == RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT) {
1193+ // Transfer RBTC
1194+ (bool success , ) = _receiver.call.value (amount)("" );
1195+ require (
1196+ success,
1197+ "FeeSharingCollector::withdrawProtocolWithheldFeesBatch: RBTC transfer failed "
1198+ );
1199+ } else {
1200+ // Transfer ERC20 token
1201+ IERC20 (token).safeTransfer (_receiver, amount);
1202+ }
1203+
1204+ emit ProtocolWithheldFeesWithdrawn (msg .sender , _receiver, token, amount);
1205+ }
1206+ }
1207+
1208+ /**
1209+ * @notice Get the accumulated protocol withheld fees for a specific token.
1210+ * @param token The token address to query.
1211+ * @return The accumulated withheld fees amount.
1212+ */
1213+ function getProtocolWithheldFees (address token ) external view returns (uint256 ) {
1214+ return protocolWithheldFees[token];
1215+ }
1216+
10981217 /**
10991218 * @dev This function is dedicated to recover the wrong fee allocation for the 4 year vesting contracts.
11001219 * This function can only be called once
0 commit comments