From d7b41208413ccee060801e61dc407dc101724cf4 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Wed, 15 Jul 2026 18:28:31 +0200 Subject: [PATCH 1/3] feat(bonding): permissionless reward calling variant Adds rewardForTranscoder / rewardForTranscoderWithHint that mint for an arbitrary active transcoder with no authorization. Mirrors PR #648 minus the auth layer: no transcoderToRewardCaller mapping, no setRewardCaller, no RewardCallerSet event, no caller check. reward()/rewardWithHint() keep their selectors and behavior (non-breaking). Spike for permissionless-vs-#648 comparison (LIP-118 discussion). Co-Authored-By: Claude Opus 4.8 (1M context) --- contracts/bonding/BondingManager.sol | 64 ++++++++++++++++++++++----- contracts/bonding/IBondingManager.sol | 12 +++++ 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/contracts/bonding/BondingManager.sol b/contracts/bonding/BondingManager.sol index 691894d0..22a66255 100644 --- a/contracts/bonding/BondingManager.sol +++ b/contracts/bonding/BondingManager.sol @@ -294,6 +294,15 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { rewardWithHint(address(0), address(0)); } + /** + * @notice Mint token rewards for an active transcoder and its delegators + * @param _transcoder Address of the transcoder on behalf of which the reward is called + * @dev Permissionless: callable by any address on behalf of any active transcoder + */ + function rewardForTranscoder(address _transcoder) external { + rewardForTranscoderWithHint(_transcoder, address(0), address(0)); + } + /** * @notice Update transcoder's fee pool. Only callable by the TicketBroker * @param _transcoder Transcoder address @@ -863,21 +872,52 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { * @param _newPosPrev Address of previous transcoder in pool if the caller is in the pool * @param _newPosNext Address of next transcoder in pool if the caller is in the pool */ - function rewardWithHint(address _newPosPrev, address _newPosNext) - public - whenSystemNotPaused - currentRoundInitialized - autoCheckpoint(msg.sender) - { + function rewardWithHint(address _newPosPrev, address _newPosNext) public { + _rewardWithHint(msg.sender, _newPosPrev, _newPosNext); + } + + /** + * @notice Mint token rewards for an active transcoder and its delegators and update the transcoder pool using an optional list hint if needed + * @dev If the `_transcoder` is in the transcoder pool, the caller can provide an optional hint for its insertion position in the + * pool via the `_newPosPrev` and `_newPosNext` params. A linear search will be executed starting at the hint to find the correct position. + * In the best case, the hint is the correct position so no search is executed. See SortedDoublyLL.sol for details on list hints + * @dev Permissionless: callable by any address on behalf of any active transcoder. Rewards always accrue to and are + * checkpointed for `_transcoder`, never the caller, so an unauthorized call can only benefit the transcoder. + * @param _transcoder Address of the transcoder on behalf of which the reward is called + * @param _newPosPrev Address of previous transcoder in pool if the `_transcoder` is in the pool + * @param _newPosNext Address of previous transcoder in pool if the `_transcoder` is in the pool + */ + function rewardForTranscoderWithHint( + address _transcoder, + address _newPosPrev, + address _newPosNext + ) public { + _rewardWithHint(_transcoder, _newPosPrev, _newPosNext); + } + + /** + * @notice Mint token rewards for an active transcoder and its delegators and update the transcoder pool using an optional list hint if needed + * @dev If the `_transcoder` is in the transcoder pool, the caller can provide an optional hint for its insertion position in the + * pool via the `_newPosPrev` and `_newPosNext` params. A linear search will be executed starting at the hint to find the correct position. + * In the best case, the hint is the correct position so no search is executed. See SortedDoublyLL.sol for details on list hints + * @param _transcoder Address of the transcoder on behalf of which the reward is called + * @param _newPosPrev Address of previous transcoder in pool if `_transcoder` is in the pool + * @param _newPosNext Address of next transcoder in pool if `_transcoder` is in the pool + */ + function _rewardWithHint( + address _transcoder, + address _newPosPrev, + address _newPosNext + ) private whenSystemNotPaused currentRoundInitialized autoCheckpoint(_transcoder) { uint256 currentRound = roundsManager().currentRound(); - require(isActiveTranscoder(msg.sender), "caller must be an active transcoder"); + require(isActiveTranscoder(_transcoder), "transcoder must be active"); require( - transcoders[msg.sender].lastRewardRound != currentRound, + transcoders[_transcoder].lastRewardRound != currentRound, "caller has already called reward for the current round" ); - Transcoder storage t = transcoders[msg.sender]; + Transcoder storage t = transcoders[_transcoder]; EarningsPool.Data storage earningsPool = t.earningsPoolPerRound[currentRound]; // Set last round that transcoder called reward @@ -910,17 +950,17 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { mtr.trustedTransferTokens(trsry, treasuryRewards); - emit TreasuryReward(msg.sender, trsry, treasuryRewards); + emit TreasuryReward(_transcoder, trsry, treasuryRewards); } uint256 transcoderRewards = totalRewardTokens.sub(treasuryRewards); - updateTranscoderWithRewards(msg.sender, transcoderRewards, currentRound, _newPosPrev, _newPosNext); + updateTranscoderWithRewards(_transcoder, transcoderRewards, currentRound, _newPosPrev, _newPosNext); // Set last round that transcoder called reward t.lastRewardRound = currentRound; - emit Reward(msg.sender, transcoderRewards); + emit Reward(_transcoder, transcoderRewards); } /** diff --git a/contracts/bonding/IBondingManager.sol b/contracts/bonding/IBondingManager.sol index b7482085..689d0f69 100644 --- a/contracts/bonding/IBondingManager.sol +++ b/contracts/bonding/IBondingManager.sol @@ -71,6 +71,18 @@ interface IBondingManager { function setCurrentRoundTotalActiveStake() external; + function reward() external; + + function rewardWithHint(address _newPosPrev, address _newPosNext) external; + + function rewardForTranscoder(address _transcoder) external; + + function rewardForTranscoderWithHint( + address _transcoder, + address _newPosPrev, + address _newPosNext + ) external; + // Public functions function getTranscoderPoolSize() external view returns (uint256); From 00489fa2c1ca9dc8328899e9fd8899a58dc159c9 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Thu, 16 Jul 2026 09:58:39 +0200 Subject: [PATCH 2/3] refactor(bonding): collapse to minimal permissionless surface Remove the private _rewardWithHint helper and the rewardForTranscoder no-hint shorthand. rewardWithHint now delegates to rewardForTranscoderWithHint with msg.sender, which carries the modifiers and body directly. Net new surface vs base: a single rewardForTranscoderWithHint(address,address,address). No auth path means both entrypoints are policy-identical, so the shared private core is redundant. Co-Authored-By: Claude Opus 4.8 (1M context) --- contracts/bonding/BondingManager.sol | 32 +++------------------------ contracts/bonding/IBondingManager.sol | 2 -- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/contracts/bonding/BondingManager.sol b/contracts/bonding/BondingManager.sol index 22a66255..e8027fea 100644 --- a/contracts/bonding/BondingManager.sol +++ b/contracts/bonding/BondingManager.sol @@ -294,15 +294,6 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { rewardWithHint(address(0), address(0)); } - /** - * @notice Mint token rewards for an active transcoder and its delegators - * @param _transcoder Address of the transcoder on behalf of which the reward is called - * @dev Permissionless: callable by any address on behalf of any active transcoder - */ - function rewardForTranscoder(address _transcoder) external { - rewardForTranscoderWithHint(_transcoder, address(0), address(0)); - } - /** * @notice Update transcoder's fee pool. Only callable by the TicketBroker * @param _transcoder Transcoder address @@ -873,7 +864,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { * @param _newPosNext Address of next transcoder in pool if the caller is in the pool */ function rewardWithHint(address _newPosPrev, address _newPosNext) public { - _rewardWithHint(msg.sender, _newPosPrev, _newPosNext); + rewardForTranscoderWithHint(msg.sender, _newPosPrev, _newPosNext); } /** @@ -885,30 +876,13 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { * checkpointed for `_transcoder`, never the caller, so an unauthorized call can only benefit the transcoder. * @param _transcoder Address of the transcoder on behalf of which the reward is called * @param _newPosPrev Address of previous transcoder in pool if the `_transcoder` is in the pool - * @param _newPosNext Address of previous transcoder in pool if the `_transcoder` is in the pool + * @param _newPosNext Address of next transcoder in pool if the `_transcoder` is in the pool */ function rewardForTranscoderWithHint( address _transcoder, address _newPosPrev, address _newPosNext - ) public { - _rewardWithHint(_transcoder, _newPosPrev, _newPosNext); - } - - /** - * @notice Mint token rewards for an active transcoder and its delegators and update the transcoder pool using an optional list hint if needed - * @dev If the `_transcoder` is in the transcoder pool, the caller can provide an optional hint for its insertion position in the - * pool via the `_newPosPrev` and `_newPosNext` params. A linear search will be executed starting at the hint to find the correct position. - * In the best case, the hint is the correct position so no search is executed. See SortedDoublyLL.sol for details on list hints - * @param _transcoder Address of the transcoder on behalf of which the reward is called - * @param _newPosPrev Address of previous transcoder in pool if `_transcoder` is in the pool - * @param _newPosNext Address of next transcoder in pool if `_transcoder` is in the pool - */ - function _rewardWithHint( - address _transcoder, - address _newPosPrev, - address _newPosNext - ) private whenSystemNotPaused currentRoundInitialized autoCheckpoint(_transcoder) { + ) public whenSystemNotPaused currentRoundInitialized autoCheckpoint(_transcoder) { uint256 currentRound = roundsManager().currentRound(); require(isActiveTranscoder(_transcoder), "transcoder must be active"); diff --git a/contracts/bonding/IBondingManager.sol b/contracts/bonding/IBondingManager.sol index 689d0f69..a90606fb 100644 --- a/contracts/bonding/IBondingManager.sol +++ b/contracts/bonding/IBondingManager.sol @@ -75,8 +75,6 @@ interface IBondingManager { function rewardWithHint(address _newPosPrev, address _newPosNext) external; - function rewardForTranscoder(address _transcoder) external; - function rewardForTranscoderWithHint( address _transcoder, address _newPosPrev, From 37ca4278530e8e4d9ed3e730a53e9e2efc396d45 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Thu, 16 Jul 2026 10:18:21 +0200 Subject: [PATCH 3/3] docs(bonding): drop contradictory 'unauthorized' in permissionless natspec Co-Authored-By: Claude Opus 4.8 (1M context) --- contracts/bonding/BondingManager.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/bonding/BondingManager.sol b/contracts/bonding/BondingManager.sol index e8027fea..28553396 100644 --- a/contracts/bonding/BondingManager.sol +++ b/contracts/bonding/BondingManager.sol @@ -872,8 +872,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { * @dev If the `_transcoder` is in the transcoder pool, the caller can provide an optional hint for its insertion position in the * pool via the `_newPosPrev` and `_newPosNext` params. A linear search will be executed starting at the hint to find the correct position. * In the best case, the hint is the correct position so no search is executed. See SortedDoublyLL.sol for details on list hints - * @dev Permissionless: callable by any address on behalf of any active transcoder. Rewards always accrue to and are - * checkpointed for `_transcoder`, never the caller, so an unauthorized call can only benefit the transcoder. + * @dev Permissionless: callable by anyone for any active transcoder. Rewards always accrue to and are checkpointed + * for `_transcoder`, never the caller, so a call can only ever benefit the transcoder. * @param _transcoder Address of the transcoder on behalf of which the reward is called * @param _newPosPrev Address of previous transcoder in pool if the `_transcoder` is in the pool * @param _newPosNext Address of next transcoder in pool if the `_transcoder` is in the pool