@@ -10,7 +10,6 @@ import "./RollupCore.sol";
1010import "../bridge/IOutbox.sol " ;
1111import "../bridge/ISequencerInbox.sol " ;
1212import "../libraries/DoubleLogicUUPSUpgradeable.sol " ;
13- import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol " ;
1413
1514contract RollupAdminLogic is RollupCore , IRollupAdmin , DoubleLogicUUPSUpgradeable {
1615 using AssertionStateLib for AssertionState;
@@ -264,20 +263,80 @@ contract RollupAdminLogic is RollupCore, IRollupAdmin, DoubleLogicUUPSUpgradeabl
264263 }
265264
266265 /**
267- * @notice Set base stake required for an assertion
268- * @param newBaseStake minimum amount of stake required
266+ * @inheritdoc IRollupAdmin
269267 */
270- function setBaseStake (
271- uint256 newBaseStake
268+ function decreaseBaseStake (
269+ uint256 newBaseStake ,
270+ uint64 latestNextInboxPosition
272271 ) external override {
273- // we do not currently allow base stake to be reduced since as doing so might allow a malicious party
272+ require (newBaseStake < baseStake, "BASE_STAKE_NOT_DECREASED " );
273+
274+ // if we're decreasing the stake we need to be more careful not to allow a malicious party
274275 // to withdraw some (up to the difference between baseStake and newBaseStake) honest funds from this contract
275276 // The sequence of events is as follows:
276277 // 1. The malicious party creates a sibling assertion, stake size is currently S
277- // 2. The base stake is then reduced to S'
278+ // 2. The base stake is then decreased to S'
278279 // 3. The malicious party uses a different address to create a child of the malicious assertion, using stake size S'
279280 // 4. This allows the malicious party to withdraw the stake S, since assertions with children set the staker to "inactive"
280- require (newBaseStake > baseStake, "BASE_STAKE_MUST_BE_INCREASED " );
281+
282+ // for this reason the base stake can only be decreased on permissioned chains where the creation of pending assertions can be controlled
283+ // In a permissionless setting, it's possible for a staker to DOS base stake decrease by creating themselves as a staker.
284+ // we can do a basic safety check to ensure that we're in the permissioned setting by checking that there is only one
285+ // staker on a pending assertion. It could be that we're in the permissionless setting and that pending assertion is a malicious one
286+ // but we assume that the rollup admin will ensure that's not the case before calling this function by checking that a staker they trust is
287+ // on a correct pending assertion.
288+ require (validatorWhitelistDisabled == false , "DECREASE_ONLY_FOR_PERMISSIONED_CHAINS " );
289+
290+ bytes32 currentConfigHash = RollupLib.configHash ({
291+ wasmModuleRoot: wasmModuleRoot,
292+ requiredStake: baseStake,
293+ challengeManager: address (challengeManager),
294+ confirmPeriodBlocks: confirmPeriodBlocks,
295+ nextInboxPosition: uint64 (latestNextInboxPosition)
296+ });
297+
298+ uint256 pendingCount = 0 ;
299+ uint64 nStakers = stakerCount ();
300+ for (uint64 i = 0 ; i < nStakers; i++ ) {
301+ bytes32 latestStaked = latestStakedAssertion (getStakerAddress (i));
302+ AssertionNode storage latestAssertion = getAssertionStorage (latestStaked);
303+ if (latestAssertion.status == AssertionStatus.Pending) {
304+ // check that in overwriting the config hash, we'll only be updating the required stake field
305+ require (latestAssertion.configHash == currentConfigHash, "EXPIRED_CONFIG_HASH " );
306+ // In order to support transitioning from permissioned to permissionless, we need to ensure that after the transition
307+ // a malicious party cannot created decreased stake assertions as children of historical (even confirmed) assertions.
308+ // To avoid this an additional check has been added in RollupUserLogic.createNewAssertion to ensure that the base stake
309+ // is always >= the required stake of the prev assertion. Having added this check we also need to override the config hash of
310+ // the latest staked assertion so that children can be created from it in the normal way. We do that below
311+ latestAssertion.configHash = RollupLib.configHash ({
312+ wasmModuleRoot: wasmModuleRoot,
313+ requiredStake: newBaseStake,
314+ challengeManager: address (challengeManager),
315+ confirmPeriodBlocks: confirmPeriodBlocks,
316+ nextInboxPosition: uint64 (latestNextInboxPosition)
317+ });
318+
319+ pendingCount++ ;
320+ }
321+
322+ require (pendingCount <= 1 , "TOO_MANY_PENDING_STAKERS " );
323+ }
324+
325+ // we need to have a non zero pending count to ensure that exactly one config hash was updated
326+ // if no config hashes are updated then no new assertions will be created, since they will all trigger STAKE_TOO_LOW
327+ require (pendingCount == 1 , "PENDING_ASSERTION_NOT_UPDATED " );
328+
329+ baseStake = newBaseStake;
330+ emit BaseStakeSet (newBaseStake);
331+ }
332+
333+ /**
334+ * @inheritdoc IRollupAdmin
335+ */
336+ function increaseBaseStake (
337+ uint256 newBaseStake
338+ ) external override {
339+ require (newBaseStake > baseStake, "BASE_STAKE_NOT_INCREASED " );
281340 baseStake = newBaseStake;
282341 emit BaseStakeSet (newBaseStake);
283342 // previously: emit OwnerFunctionCalled(12);
0 commit comments