Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5b012cf
chore(foundry): default to via_ir = false
May 8, 2026
30f7d5c
refacto(utils): add SECONDS_PER_YEAR constant and Pause errors
May 8, 2026
f363ab2
refacto(parallelizer): split togglePause into pause and unpause
May 8, 2026
f437867
chore(foundry): default to via_ir = false
May 8, 2026
b3a581e
refacto(utils): add SECONDS_PER_YEAR constant and Pause errors
May 8, 2026
d85eb84
fix(savings): track storedAssets to prevent donation attacks
May 8, 2026
64923d1
test(savings): cover donation attacks, recoverSurplus and upgrade path
May 8, 2026
a1204e1
Merge pull request #25 from parallel-protocol/refactor/parallelizer-p…
FabienCoutant May 27, 2026
4baa958
Merge pull request #26 from parallel-protocol/fix/savings-donation-at…
FabienCoutant May 27, 2026
ad83408
style(savings): wrap long abi.encodeWithSelector line to satisfy solh…
May 27, 2026
6d5d279
Merge lint fix from fix/savings-donation-attack
May 27, 2026
1953ce3
test(invariants): stabilize path-independence vs intended I-6 path de…
May 27, 2026
8f47e85
Merge path-independence invariant stabilization from test/path-indepe…
May 27, 2026
2310b90
fix(invariants): drop redeem bound (stack), keep widened path-indepen…
May 27, 2026
8a9c2ac
chore: remove accidentally committed local script AddCollateralMUSDC
May 27, 2026
7c24bc4
Merge dev (path-independence tolerance fix #28) into integration branch
May 27, 2026
33b7a00
docs(audits): add Cyfrin Parallel Savings Fix 1st report
Jun 5, 2026
7468f4e
fix(Savings) [Cyfrin-I4]: set lastUpdate in initialize to bound first…
Jun 5, 2026
fcbc460
fix(Savings) [Cyfrin-I3]: restrict initializeStoredAssets to governance
Jun 5, 2026
ed6ae54
fix(Savings) [Cyfrin-L1]: bound storedAssets seeding to a lastUpdate …
Jun 5, 2026
0e81ba4
fix(Savings) [Cyfrin-L2]: drop paused interval on unpause so pausing …
Jun 5, 2026
ec66a0d
docs(audits): add Cyfrin Parallel Savings Fix final report
Jun 10, 2026
61942aa
Merge pull request #29 from parallel-protocol/audit/cyfrin-june-2026-…
FabienCoutant Jun 10, 2026
011211f
docs(audits): add Bailsec Parallel Savings Fix 1st report
Jun 26, 2026
c078aea
fix(Savings) [Bailsec-01]: grant initializeStoredAssets selector to g…
Jun 26, 2026
90428c0
fix(Savings) [Bailsec-02]: re-anchor lastUpdate in initializeStoredAs…
Jun 26, 2026
a3c5530
fix(Savings) [Bailsec-03]: return storedAssets from totalAssets while…
Jun 26, 2026
c4aebc2
fix(Savings) [Bailsec-04]: skip accrual while paused so rate setters …
Jun 26, 2026
d87db00
fix(Savings) [Bailsec-05]: enforce ERC4626 max checks in deposit/mint…
Jun 26, 2026
1276221
fix(Savings) [Bailsec-07]: guard entry points against uninitialized s…
Jun 26, 2026
f0dde86
fix(Savings) [Bailsec-05]: extend ERC4626 max checks to EIP-3009 auth…
Jul 6, 2026
21a1b7a
docs(audits): add Bailsec Parallel Savings Fix final report
Jul 6, 2026
aeb3262
Merge pull request #31 from parallel-protocol/audit/bailsec-june-2026…
FabienCoutant Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions contracts/interfaces/ISavings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,19 @@ interface ISavings is IEIP3009 {
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);

function estimatedAPR() external view returns (uint256 apr);
function estimatedAPY() external view returns (uint256 apy);

function computeUpdatedAssets(uint256 totalAssets, uint256 exp) external view returns (uint256);

function togglePause() external;
function pause() external;

function unpause() external;

function toggleTrusted(address trustedAddress) external;

function setRate(uint208 newRate) external;

function setMaxRate(uint256 newMaxRate) external;

function recoverSurplus(address to) external returns (uint256 surplus);
}
9 changes: 7 additions & 2 deletions contracts/interfaces/ISetters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,13 @@ interface ISettersGovernor {
/// @author Cooper Labs
/// @custom:contact security@cooperlabs.xyz
interface ISettersGuardian {
/// @notice Changes the pause status for mint or burn transactions for `collateral`
function togglePause(address collateral, ActionType action) external;
/// @notice Pauses `action` for `collateral` (or pauses redemption protocol-wide when
/// `action == Redeem`). Reverts if the action is already paused.
function pause(address collateral, ActionType action) external;

/// @notice Unpauses `action` for `collateral` (or unpauses redemption protocol-wide when
/// `action == Redeem`). Reverts if the action is not paused.
function unpause(address collateral, ActionType action) external;

/// @notice Sets the mint or burn fees for `collateral`
function setFees(address collateral, uint64[] memory xFee, int64[] memory yFee, bool mint) external;
Expand Down
6 changes: 3 additions & 3 deletions contracts/parallelizer/configs/DiamondInitializer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ contract DiamondInitializer {
LibSetters.setFees(collateral.token, collateral.xMintFee, collateral.yMintFee, true);
// Burn fees
LibSetters.setFees(collateral.token, collateral.xBurnFee, collateral.yBurnFee, false);
LibSetters.togglePause(collateral.token, ActionType.Mint);
LibSetters.togglePause(collateral.token, ActionType.Burn);
LibSetters.unpause(collateral.token, ActionType.Mint);
LibSetters.unpause(collateral.token, ActionType.Burn);
LibSetters.setStablecoinCap(collateral.token, 100_000_000 ether);
if (collateral.targetMax) LibOracle.updateOracle(collateral.token);
}

// setRedemptionCurveParams
if (_redemptionSetup.xRedeemFee.length > 0) {
LibSetters.togglePause(address(0), ActionType.Redeem);
LibSetters.unpause(address(0), ActionType.Redeem);
LibSetters.setRedemptionCurveParams(_redemptionSetup.xRedeemFee, _redemptionSetup.yRedeemFee);
}
}
Expand Down
14 changes: 7 additions & 7 deletions contracts/parallelizer/configs/Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ contract Test {
yMintFee[3] = int64(uint64(BASE_12 - 1));

LibSetters.setFees(eurA.collateral, xMintFee, yMintFee, true);
LibSetters.togglePause(eurA.collateral, ActionType.Mint);
LibSetters.unpause(eurA.collateral, ActionType.Mint);

uint64[] memory xBurnFee = new uint64[](4);
xBurnFee[0] = uint64(BASE_9);
Expand All @@ -85,7 +85,7 @@ contract Test {
yBurnFee[3] = int64(uint64(MAX_BURN_FEE - 1));

LibSetters.setFees(eurA.collateral, xBurnFee, yBurnFee, false);
LibSetters.togglePause(eurA.collateral, ActionType.Burn);
LibSetters.unpause(eurA.collateral, ActionType.Burn);

// Setup second collateral
LibSetters.addCollateral(eurB.collateral);
Expand Down Expand Up @@ -121,7 +121,7 @@ contract Test {
yMintFee[3] = int64(uint64(BASE_12 - 1));

LibSetters.setFees(eurB.collateral, xMintFee, yMintFee, true);
LibSetters.togglePause(eurB.collateral, ActionType.Mint);
LibSetters.unpause(eurB.collateral, ActionType.Mint);

xBurnFee = new uint64[](4);
xBurnFee[0] = uint64(BASE_9);
Expand All @@ -137,7 +137,7 @@ contract Test {
yBurnFee[3] = int64(uint64(MAX_BURN_FEE - 1));

LibSetters.setFees(eurB.collateral, xBurnFee, yBurnFee, false);
LibSetters.togglePause(eurB.collateral, ActionType.Burn);
LibSetters.unpause(eurB.collateral, ActionType.Burn);

// Setup third collateral
LibSetters.addCollateral(eurY.collateral);
Expand Down Expand Up @@ -173,7 +173,7 @@ contract Test {
yMintFee[3] = int64(uint64(BASE_12 - 1));

LibSetters.setFees(eurY.collateral, xMintFee, yMintFee, true);
LibSetters.togglePause(eurY.collateral, ActionType.Mint);
LibSetters.unpause(eurY.collateral, ActionType.Mint);

xBurnFee = new uint64[](4);
xBurnFee[0] = uint64(BASE_9);
Expand All @@ -189,14 +189,14 @@ contract Test {
yBurnFee[3] = int64(uint64(MAX_BURN_FEE - 1));

LibSetters.setFees(eurY.collateral, xBurnFee, yBurnFee, false);
LibSetters.togglePause(eurY.collateral, ActionType.Burn);
LibSetters.unpause(eurY.collateral, ActionType.Burn);

// Set no hard limits on stablecoin minting per collateral
LibSetters.setStablecoinCap(eurA.collateral, type(uint256).max);
LibSetters.setStablecoinCap(eurB.collateral, type(uint256).max);
LibSetters.setStablecoinCap(eurY.collateral, type(uint256).max);

// Redeem
LibSetters.togglePause(eurA.collateral, ActionType.Redeem);
LibSetters.unpause(eurA.collateral, ActionType.Redeem);
}
}
9 changes: 7 additions & 2 deletions contracts/parallelizer/facets/SettersGuardian.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ import "../Storage.sol";
/// https://github.com/AngleProtocol/angle-transmuter/blob/main/contracts/transmuter/facets/SettersGuardian.sol
contract SettersGuardian is AccessManagedModifiers, ISettersGuardian {
/// @inheritdoc ISettersGuardian
function togglePause(address collateral, ActionType pausedType) external restricted {
LibSetters.togglePause(collateral, pausedType);
function pause(address collateral, ActionType action) external restricted {
LibSetters.pause(collateral, action);
}

/// @inheritdoc ISettersGuardian
function unpause(address collateral, ActionType action) external restricted {
LibSetters.unpause(collateral, action);
}

/// @inheritdoc ISettersGuardian
Expand Down
40 changes: 30 additions & 10 deletions contracts/parallelizer/libraries/LibSetters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -183,25 +183,45 @@ library LibSetters {
ONLY GUARDIAN ACTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/

/// @notice Internal version of `togglePause`
function togglePause(address collateral, ActionType action) internal {
uint8 isLive;
/// @notice Internal version of `pause` — sets the action's live flag to `0`
/// @dev Reverts with `AlreadyPaused` if the action is already paused so a no-op governance
/// call cannot pass silently
function pause(address collateral, ActionType action) internal {
_setPauseState(collateral, action, 0);
}

/// @notice Internal version of `unpause` — sets the action's live flag to `1`
/// @dev Reverts with `NotPaused` if the action is already unpaused so a no-op governance
/// call cannot pass silently
function unpause(address collateral, ActionType action) internal {
_setPauseState(collateral, action, 1);
}

/// @dev Shared accessor for `pause` and `unpause`. `targetIsLive` is `0` to pause and `1`
/// to unpause. Reverts on no-op transitions.
function _setPauseState(address collateral, ActionType action, uint8 targetIsLive) private {
if (action == ActionType.Mint || action == ActionType.Burn) {
Collateral storage collatInfo = s.transmuterStorage().collaterals[collateral];
if (collatInfo.decimals == 0) revert NotCollateral();
uint8 currentIsLive = action == ActionType.Mint ? collatInfo.isMintLive : collatInfo.isBurnLive;
if (currentIsLive == targetIsLive) {
if (targetIsLive == 0) revert AlreadyPaused();
revert NotPaused();
}
if (action == ActionType.Mint) {
isLive = 1 - collatInfo.isMintLive;
collatInfo.isMintLive = isLive;
collatInfo.isMintLive = targetIsLive;
} else {
isLive = 1 - collatInfo.isBurnLive;
collatInfo.isBurnLive = isLive;
collatInfo.isBurnLive = targetIsLive;
}
} else {
ParallelizerStorage storage ts = s.transmuterStorage();
isLive = 1 - ts.isRedemptionLive;
ts.isRedemptionLive = isLive;
if (ts.isRedemptionLive == targetIsLive) {
if (targetIsLive == 0) revert AlreadyPaused();
revert NotPaused();
}
ts.isRedemptionLive = targetIsLive;
}
emit PauseToggled(collateral, uint256(action), isLive == 0);
emit PauseToggled(collateral, uint256(action), targetIsLive == 0);
}

/// @notice Internal version of `setFees`
Expand Down
Loading
Loading